dynamic_cast en tiempo de compilación

Saludos a todos los lectores.







¿Cuál es el artículo (o la tarea del artículo) : una respuesta práctica a la pregunta "es posible crear un gran proyecto de tal manera que se abandone por completo dynamic_cast



en la etapa de ejecución?", Donde por un gran proyecto significa uno en el que ya no hay una persona que se quedaría con el código completo toda la base del proyecto.







Respuesta preliminar : SÍ, es posible: es posible crear un mecanismo que le permita resolver el problema de dynamic_cast en la etapa de compilación , pero es poco probable que esto se aplique en la práctica por razones como: (1) desde el principio, el proyecto de destino debe construirse de acuerdo con reglas predeterminadas, como resultado qué tomar y aplicar la técnica a un proyecto existente, consume mucho tiempo (2) un aumento significativo en la complejidad del código desde el punto de vista de su legibilidad en ciertos lugares, donde, de hecho, se reemplaza la lógicadynamic_cast



al uso de las plantillas que se proponen a continuación (3), que pueden resultar inaceptables en algunos proyectos por razones ideológicas de los responsables (4) el interés del autor es únicamente dar respuesta a la pregunta planteada, y no crear un mecanismo universal y conveniente para resolver la tarea (después de todo, en la práctica no es necesario resolver problemas que no son urgentes).







Idea de implementación



Se basó en la idea de una lista de tipos, descrita por Andrei Alexandrescu e implementada por él en la biblioteca Loki . Esta idea se ha refinado en los siguientes puntos (los puntos marcados *



significan que en este punto el autor del artículo no está de acuerdo con la visión de Alexandrescu de las listas de tipos):







  • agregó la capacidad de generar una lista de tipos de longitud arbitraria sin usar macros y / o estructuras de plantilla, con el número de parámetros de plantilla igual a la longitud de la lista creada;
  • agregó la capacidad de generar una lista de tipos basada en el tipo (s) y / o una lista existente de tipos en su combinación arbitraria;
  • * eliminó la capacidad de crear listas de tipos cuyos elementos pueden ser listas de tipos;
  • * 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



    — .

    , .







    Gracias a todos los que leyeron el artículo :) - Estaré encantado de conocer su experiencia, opinión o, tal vez, incluso la solución al problema descrito en el artículo en los comentarios.








All Articles