Saudações a todos os leitores.
Qual é o artigo (ou a tarefa do artigo) : uma resposta prática à pergunta "é possível criar um grande projeto de forma a abandoná-lo completamente dynamic_cast
na fase de execução?", Onde por grande projeto significa aquele em que não há mais uma pessoa que manteria todo o código toda a base do projeto.
Resposta preliminar : SIM, é possível - é possível criar um mecanismo que permite resolver o problema dynamic_cast na fase de compilação , mas - é improvável que seja aplicado na prática por razões como: (1) desde o início, o projeto alvo deve ser construído de acordo com regras predeterminadas, como resultado o que pegar e aplicar a técnica a um projeto existente, consome muito tempo (2) um aumento significativo na complexidade do código do ponto de vista de sua legibilidade em determinados locais, onde, de fato, a lógica é substituídadynamic_cast
ao uso dos modelos propostos a seguir (3), que podem ser inaceitáveis em alguns projetos por motivos ideológicos dos seus responsáveis (4) o interesse do autor é apenas em dar uma resposta à questão colocada, e não em criar um mecanismo universal e conveniente de resolução a tarefa (afinal, na prática não é necessário resolver problemas que não sejam urgentes).
Ideia de implementação
Foi baseado na ideia de uma lista de tipos, descrita por Andrei Alexandrescu e implementada por ele na biblioteca Loki . Esta ideia foi refinada nos seguintes pontos (os pontos marcados *
significam que neste ponto o autor do artigo não concorda com a visão de Alexandrescu sobre as listas de tipos):
- adicionada a capacidade de gerar uma lista de comprimento arbitrário de tipos sem usar macros e / ou estruturas de modelo, com o número de parâmetros de modelo igual ao comprimento da lista criada;
- adicionada a capacidade de gerar uma lista de tipos com base no (s) tipo (s) e / ou lista (s) existente (s) de tipos em sua combinação arbitrária;
- * removida a capacidade de criar listas de tipos cujos elementos podem 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();
}
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
.
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
— , .
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
, .
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
.
-
,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
evalue
.
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
— .
, .
Obrigado a todos que leram o artigo :) - Ficarei feliz em saber sua experiência, opinião sobre, ou, talvez, até mesmo a solução para o problema descrito no artigo nos comentários.