Wskaźniki i polimorfizm klas szablonowych

0

Witam.

Mam klasę szablonową typu variadic z jedną specjalizacją:

template<typename... T>
struct Base
{
	int X;
};

template<typename First, typename... Rest>
struct Base<First, Rest...> : public First, public Base<Rest...>
{

};

Całość działa tak, że pusta klasa Base (ta ze specjalizacją) dziedziczy rekursywnie po wszystkich elementach, a na samym końcu (gdy zostaje już tylko jeden element) dziedziczy sama po sobie, ale w wersji podstawowej (tej z zawartością):

struct Base : public First, public Base<>
{

};

Dodatkowo używam takiego modelu, by korzystać ze static_assert() dla każdego First i sprawdzać, czy wskazany typ spełnia odpowiednie kryteria (tutaj to pominąłem, żeby nie zaśmiecać kodu). Poniżej przykładowe klasy wsadowe:

struct Addon1
{
	int Value1;
};

struct Addon2
{
	int Value2;
};

Sęk w tym, że chciałbym potem przetrzymywać takie klasy w kontenerze wskaźników (np. std::vector) i używając dowcastów i upcastów dokopywać się do składników bazowych. Niestety nie mogę sobie poradzić z odpowiednim rzutowaniem:

Base<Addon1, Addon2> B;
B.X = 123;
B.Value1 = 10;
B.Value2 = 20;

Base<>* BasePtr = &B;
std::cout << BasePtr->X << std::endl; //Wyświetla 123 = OK
std::cout << reinterpret_cast<Addon2*>(BasePtr)->Value2 << std::endl; //Wyświetla 123 = błąd, powinno być 20

void* AnyPtr = &B;
std::cout << reinterpret_cast<Base<>*>(AnyPtr)->X << std::endl; //Wyświetla 10 = błąd, powinno być 123
std::cout << reinterpret_cast<Addon2*>(AnyPtr)->Value2 << std::endl; //Wyświetla 10 = błąd, powinno być 20

A co konkretnie próbuję osiągnąć? Mam klasę SpriteEx<>, która może zostać rozbudowana o dodatkowe efekty, np. SpriteEx<Saturation, Contrast>. Dzięki modułowości klasa jest lżejsza, co przy setkach czy nawet tysiącach instancji jednocześnie przechowywanych w pamięci ma znaczenie, zwłaszcza, że wszystkich efektów jest ponad 50, a każdy z nich dodaje od jednego do kilku floatów, przy czym gotowy Sprite średnio korzysta tylko z 0-3 efektów naraz. Takie gotowe Sprite'y chcę przechowywać na liście, by klasa renderująca mogła przelecieć ją w pętli i w zależności od specjalnych flag rozpoznać jakie rozszerzenia (efekty) wchodzą w skład klasy i je zastosować (narysować), używając rzutowania.

Ktoś mógłby podpowiedzieć, co robię źle i jak to powinienem rozwiązać?

1

Problemem przy takim castowaniu:

reinterpret_cast<Addon2*>(BasePtr)

Jest to, że typ Addon2 nie jest w żaden sposób związany z Base<>. Takie powiązanie dopiero jest w typie Base< Addon1, Addon2 >.

Tak jak to robisz to niemożliwe jest odpowiednie rzutowanie typów, bo nie wiadomo gdzie jest Addon2 w pamięci.
Rozważ następujący kod:

Base< Addon2, Addon1 > A;
Base< Addon1, Addon2 > B;

Base<>* BasePtrA = &A;
Base<>* BasePtrB = &B;

std::cout << offsetof( decltype( A ), Value1 ) << std::endl; // printuje 4
std::cout << offsetof( decltype( B ), Value1 ) << std::endl; // printuje 0

cast< Addon2 * >( BasePtrA )->Value2; // Skąd kompilator ma wiedzieć gdzie jest Addon2?
cast< Addon2 * >( BasePtrB )->Value2; // Skąd kompilator ma wiedzieć gdzie jest Addon2?

Żeby takie rzutowanie było prawidłowe, musiałbyś wskazać kompilatorowi gdzie jest Addon2:

std::cout << static_cast< Addon2 * >( static_cast< Base< Addon1, Addon2 > * >( BasePtr ) )->Value2 << std::endl; 
0

Całość działa tak, że pusta klasa Base (ta ze specjalizacją) dziedziczy rekursywnie po wszystkich elementach

Nic takiego nie ma miejsca, możesz sam sobie sprawdził na cpp insights w jaki sposób kompilator rozwija Twoje szablony https://cppinsights.io/s/63093e84
Takie dziedziczenie po typach można osiągnąć dużo prościej https://cppinsights.io/s/98a8a1a6
Ale czy to jest właściwe rozwiązanie Twojego problemu to nie wiem.

struct type{};
struct type_2{};
struct type_3{};

template<typename ...T>
class Child : public T...
{};

template<>
class Child<> {};

int main()
{
    Child<type, type_2, type_3, Child<>> child;
}
3

IMHO czym mógłbyś się zainteresować to jak zaimplementować statyczny Entity Component System używając templateów w C++.

Link: https://indiegamedev.net/2020/05/19/an-entity-component-system-with-data-locality-in-cpp/

Przykładowy kod z linka:

struct Position
{
    float x;
    float y;
};

struct Velocity
{
    float x;
    float y;
};

struct Randomness
{
    float a;
};

int main()
{
    ECS ecs;

    ecs.RegisterComponent<Position>();
    ecs.RegisterComponent<Velocity>();
    ecs.RegisterComponent<Randomness>();

    Entity ent1(ecs);
    ent1.Add<Position>({1,2});
    ent1.Add<Velocity>({.5f,.5f});
    ent1.Add<Randomness>({.25f});

    Entity ent2(ecs);
    ent2.Add<Position>({5,24});
    ent2.Add<Randomness>({0.8f});

    System<Position,Velocity> system(ecs);
    system.Action([](const float elapsedMilliseconds,
                     const std::vector<EntityID>& entities,
                     Position* p,
                     Velocity* v)
    {
        for(std::size_t i = 0; i < entities.size(); ++i)
        {
            std::cout   << i << " PV -----------------\n"
                        << "P: (" << p[i].x << ',' << p[i].y << ")\n"
                        << "V: {" << v[i].x << ',' << v[i].y << ")"
                        << std::endl;

            p[i].x += v[i].x * (elapsedMilliseconds / 1000.f);
            p[i].y += v[i].y * (elapsedMilliseconds / 1000.f);
        }
    });

    System<Randomness, Position> system2(ecs);
    system2.Action([](const float elapsedMilliseconds,
                     const std::vector<EntityID>& entities,
                     Randomness* r,
                     Position* p)
    {
        for(std::size_t i = 0; i < entities.size(); ++i)
        {
            p[i].x -= r[i].a;
            std::cout   << i << " RP -----------------\n"
                        << "R: (" << r[i].a << ")\n"
                        << "P: {" << p[i].x << ',' << p[i].y << ")"
                        << std::endl;
        }
    });

    for(int i = 0; i < 3; ++i)
        ecs.RunSystems(1000);

    ecs.RemoveComponent<Randomness>(ent2.GetID());

    for(int i = 0; i < 3; ++i)
        ecs.RunSystems(1000);

    ecs.RemoveEntity(ent1.GetID());

    for(int i = 0; i < 3; ++i)
        ecs.RunSystems(1000);

    return 0;
}

To jest coś co spełniłoby twoje wymagania i szybko by działało (również byłoby łatwe do zrównoleglenia).

1 użytkowników online, w tym zalogowanych: 0, gości: 1