Я пытаюсь сделать некоторые трюки с информацией о типах во время выполнения с помощью множественного наследования и шаблонов, но я столкнулся с проблемой неоднозначности.
Сначала приведу очень простой пример того, о чем я говорю:
#include <memory>
struct A { };
struct B : public A { };
struct C : public B, public A { };
int main() {
std::shared_ptr<A> p0 = std::make_shared<B>(); // possible
std::shared_ptr<A> p1 = std::make_shared<C>(); // impossible
// Here the compiler obviously whines "‘A’ is an ambiguous base of ‘C’"
// but if we're especially naughty, we can circumvent it with a void* cast
// we still can't really use this pointer to anything though.
A* p2 = static_cast<A*>((void*)new C());
}
Как свидетельствуют комментарии, я могу быть особенно непослушным и полностью игнорировать тип. Эта порочность приведет меня прямо в ад разработчика и не позволит мне использовать указатель так, как мне бы того хотелось, так что на самом деле у меня все в порядке с базой std::shared_ptr<B>
. Но второй (и немного ближе к реальности) пример показывает, что этого компромисса недостаточно:
#include <iostream>
#include <memory>
int current_val = 0;
struct A {
int runtime_val;
A(int v) : runtime_val(v)
{
std::cout << "ACtor with arg: " << v << std::endl;
}
};
template<typename T>
struct B : public A {
static const int global_val;
B() : A(global_val) {}
};
template<typename T>
const int B<T>::global_val{current_val++};
struct X : public B<X> {}; // Base
struct C : public X, public B<C> {}; // Leaf
struct D : public X, public B<D> {}; // Leaf
int main() {
std::cout << "Xval: " << B<X>::global_val << std::endl; // 0
std::cout << "Dval: " << B<D>::global_val << std::endl; // 1
std::cout << "Cval: " << B<C>::global_val << std::endl; // 2
// std::shared_ptr<A> px = std::make_shared<X>(); // Impossible because of ambiguity
std::shared_ptr<X> p0 = std::make_shared<X>(); // I'm fine with this, really
std::shared_ptr<X> p1 = std::make_shared<D>();
std::shared_ptr<X> p2 = std::make_shared<C>();
std::cout << "p0 val: " << p0->runtime_val << " (X)" << std::endl; // 0 :)
std::cout << "p1 val: " << p0->runtime_val << " (D)" << std::endl; // 0 :(
std::cout << "p2 val: " << p0->runtime_val << " (C)" << std::endl; // 0 >:(
}
В этом примере я использую B<T>::global_val
в качестве своего рода информации о типе среды выполнения для данного типа <T>
. Когда программа запускается, я получаю следующий вывод (для ясности я добавил несколько дополнительных комментариев):
Xval: 0
Dval: 1
Cval: 2
ACtor with arg: 0 -- (p0 initialization)
ACtor with arg: 0 -- (p1 initialization (X base))
ACtor with arg: 1 -- (p1 initialization (D base))
ACtor with arg: 0 -- (p2 initialization (X base))
ACtor with arg: 2 -- (p2 initialization (C base))
p0 val: 0 (X)
p1 val: 0 (D)
p2 val: 0 (C)
Кажется, что v-таблица хочет указывать только на X
-базу моегоD
и C
классы. Как я могу гарантировать, что D
и C
экземпляры runtime_val
будут указывать на листья дерева наследования, а не на базу?
PS Я пытался создать базовый класс X
чисто виртуальный, но не повезло.