dynamic_cast at compile time

Greetings to all readers.







What is the article (or the task of the article) : a practical answer to the question "is it possible to create a large project in such a way as to completely abandon it dynamic_cast



at the stage of execution?" the entire project base.







Preliminary answer : YES, it is possible - it is possible to create a mechanism that allows you to solve the dynamic_cast problem at the compilation stage , but - this is unlikely to be applied in practice for reasons such as: (1) from the very beginning, the target project should be built according to predetermined rules, as a result what to take and apply the technique to an existing project, it is very time consuming (2) a significant increase in the complexity of the code from the point of view of its readability in certain places, where, in fact, the logic is replaceddynamic_cast



to the use of templates proposed below (3), which may be unacceptable in some projects for ideological reasons of those responsible for it (4) the author's interest is solely in giving an answer to the question posed, and not in creating a universal and convenient mechanism for solving the task (after all, it is not necessary in practice to solve problems that are not urgent).







Implementation idea



It was based on the idea of ​​a list of types, described by Andrei Alexandrescu and implemented by him in the Loki library . This idea has been refined on the following points (marked points *



mean that on this point the author of the article does not agree with Alexandrescu's vision of type lists):







  • added the ability to generate an arbitrary length list of types without using macros and / or template structures, with the number of template parameters equal to the length of the created list;
  • added the ability to generate a list of types based on the type (s) and / or an existing list (s) of types in their arbitrary combination;
  • * removed the ability to create lists of types whose elements can be lists of types;
  • * MostDerived



    DerivedToFront



    , .. (1) , , , , , (2) , , - , , ;
  • static_assert



    , , ;
  • RemoveFromSize



    , CutFromSize



    .

    , , (https://github.com/AlexeyPerestoronin/Cpp_TypesList), , , .


, , , .







#include <gtest/gtest.h>
#include <TypesList.hpp>

#include <memory>

class A {
    public:
    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<void>>;

    A() {}
    A(int a) {
        buffer << ' ' << a;
    }

    virtual void F1() = 0;

    protected:
    std::stringstream buffer;
};

class B : public A {
    public:
    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<A, A::BASE_t>>;

    B() {}
    B(int a, int b)
        : A(a) {
        buffer << ' ' << b;
    }

    virtual void F1() override {
        std::cout << "class::B" << buffer.str() << std::endl;
    }
};

class C : public B {
    public:
    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<B, B::BASE_t>>;

    C() {}
    C(int a, int b, int c)
        : B(a, b) {
        buffer << ' ' << c;
    }

    virtual void F1() override {
        std::cout << "class::C" << buffer.str() << std::endl;
    }
};

class D : public C {
    public:
    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<C, C::BASE_t>>;

    D() {}
    D(int a, int b, int c, int d)
        : C(a, b, c) {
        buffer << ' ' << d;
    }

    virtual void F1() override {
        std::cout << "class::D" << buffer.str() << std::endl;
    }
};

TEST(Check_class_bases, test) {
    {
        using TClass = A;
        EXPECT_EQ(TClass::BASE_t::size, 1);
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));
    }
    {
        using TClass = B;
        EXPECT_EQ(TClass::BASE_t::size, 2);
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));
    }
    {
        using TClass = C;
        EXPECT_EQ(TClass::BASE_t::size, 3);
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, B>));
    }
    {
        using TClass = D;
        EXPECT_EQ(TClass::BASE_t::size, 4);
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, C>));
    }
}

// TT - Type to Type
template<class Type, class BASE_t>
struct T2T {
    std::shared_ptr<Type> value;
    using PossibleTo_t = BASE_t;
};

template<class To, class From, class... Arguments>
auto T2TMake(Arguments&&... arguments) {
    T2T<To, TL::Refine_R<TL::CreateTypesList_R<From, From::BASE_t>>> result{};
    result.value = std::make_shared<From>(arguments...);
    return result;
}

template<class BASE_t>
void AttemptUse(T2T<A, BASE_t> tb) {
    static_assert(TL::IsInList_R<BASE_t, C>, "this function can to use only with C-derivative params");
    tb.value->F1();
}

TEST(T2TMake, test) {
    {
        auto value = T2TMake<A, B>();
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 3);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        // AttemptUse(value); // compilation error
    }
    {
        auto value = T2TMake<A, B>(1, 2);
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 3);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        // AttemptUse(value); // compilation error
    }
    {
        auto value = T2TMake<A, C>();
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 4);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass, C>));
        AttemptUse(value);
    }
    {
        auto value = T2TMake<A, C>(1, 2, 3);
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 4);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass, C>));
        AttemptUse(value);
    }
    {
        auto value = T2TMake<A, D>();
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 5);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass, C>));
        EXPECT_TRUE((TL::IsInList_R<TClass, D>));
        AttemptUse(value);
    }
    {
        auto value = T2TMake<A, D>(1, 2, 3, 4);
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 5);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass, C>));
        EXPECT_TRUE((TL::IsInList_R<TClass, D>));
        AttemptUse(value);
    }
}

int main(int argc, char* argv[]) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
      
      





  1. class A













    class A {
    public:
    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<void>>;
    
    A() {}
    A(int a) {
        buffer << ' ' << a;
    }
    
    virtual void F1() = 0;
    
    protected:
    std::stringstream buffer;
    };
          
          





    class A



    β€” - , : using BASE_t = TL::Refine_R<TL::CreateTypesList_R<void>>;



    , .

    :







    • TL::CreateTypesList_R



      β€” , .
    • TL::Refine_R



      β€” , , .

      .. , void



      .


  2. class B













    class B : public A {
    public:
    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<A, A::BASE_t>>;
    
    B() {}
    B(int a, int b)
        : A(a) {
        buffer << ' ' << b;
    }
    
    virtual void F1() override {
        std::cout << "class::B" << buffer.str() << std::endl;
    }
    };
          
          





    , , BASE_t



    β€” , .







  3. class C













    class C : public B {
    public:
    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<B, B::BASE_t>>;
    
    C() {}
    C(int a, int b, int c)
        : B(a, b) {
        buffer << ' ' << c;
    }
    
    virtual void F1() override {
        std::cout << "class::C" << buffer.str() << std::endl;
    }
    };
          
          





    , , BASE_t



    , .







  4. class D













    class D : public C {
    public:
    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<C, C::BASE_t>>;
    
    D() {}
    D(int a, int b, int c, int d)
        : C(a, b, c) {
        buffer << ' ' << d;
    }
    
    virtual void F1() override {
        std::cout << "class::D" << buffer.str() << std::endl;
    }
    };
          
          





    , D BASE_t



    .









  5. , TL::IsInList_R<TypesList, Type>



    true



    , Type



    TypesList



    , false



    β€” .



    TEST(Check_class_bases, test) {
    {
        using TClass = A;
        EXPECT_EQ(TClass::BASE_t::size, 1);
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));
    }
    {
        using TClass = B;
        EXPECT_EQ(TClass::BASE_t::size, 2);
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));
    }
    {
        using TClass = C;
        EXPECT_EQ(TClass::BASE_t::size, 3);
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, B>));
    }
    {
        using TClass = D;
        EXPECT_EQ(TClass::BASE_t::size, 4);
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, C>));
    }
    }
          
          





    , : class A



    , class B



    , class C



    class D



    , β€” BASE_t



    .











    // T2T - Type to Type
    template<class Type, class BASE_t>
    struct T2T {
    std::shared_ptr<Type> value;
    using PossibleTo_t = BASE_t;
    };
          
          





    value



    Type



    PossibleTo_t



    value



    , ( ) Type



    .







    T2T







    template<class To, class From, class... Arguments>
    auto T2TMake(Arguments&&... arguments) {
    T2T<To, TL::Refine_R<TL::CreateTypesList_R<From, From::BASE_t>>> result{};
    result.value = std::make_shared<From>(arguments...);
    return result;
    }
          
          





    T2TMake



    :





    • From



      β€” , T2T



      ;
    • To



      β€” T2T



      ;
    • Arguments



      β€” .

      , , From



      To



      , TL::Refine_R<TL::CreateTypesList_R<From, From::BASE_t>>



      T2T



      e value



      .


    T2T







    template<class BASE_t>
    void AttemptUse(T2T<A, BASE_t> tb) {
    static_assert(TL::IsInList_R<BASE_t, C>, "this function can to use only with C-derivative params");
    tb.value->F1();
    }
          
          





    , , , , β€” , β€” .











    TEST(T2TMake, test) {
    {
        auto value = T2TMake<A, B>();
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 3);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        // AttemptUse(value); // compilation error
    }
    {
        auto value = T2TMake<A, B>(1, 2);
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 3);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        // AttemptUse(value); // compilation error
    }
    {
        auto value = T2TMake<A, C>();
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 4);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass, C>));
        AttemptUse(value);
    }
    {
        auto value = T2TMake<A, C>(1, 2, 3);
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 4);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass, C>));
        AttemptUse(value);
    }
    {
        auto value = T2TMake<A, D>();
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 5);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass, C>));
        EXPECT_TRUE((TL::IsInList_R<TClass, D>));
        AttemptUse(value);
    }
    {
        auto value = T2TMake<A, D>(1, 2, 3, 4);
        using TClass = decltype(value)::PossibleTo_t;
        EXPECT_EQ(TClass::size, 5);
        EXPECT_TRUE((TL::IsInList_R<TClass, void>));
        EXPECT_TRUE((TL::IsInList_R<TClass, A>));
        EXPECT_TRUE((TL::IsInList_R<TClass, B>));
        EXPECT_TRUE((TL::IsInList_R<TClass, C>));
        EXPECT_TRUE((TL::IsInList_R<TClass, D>));
        AttemptUse(value);
    }
    }
          
          









    dynamic_cast



    β€” .

    , .







    Thanks to everyone who read the article :) - I will be glad to know your experience, opinion on, or, perhaps, even the solution to the problem described in the article in the comments.








All Articles