Неоднозначность при множественном наследовании в C++

Опишу одну из непопулярных сторон C++. Язык поддерживает множественное наследование и, соответственно, в нем существует т.н. проблема ромба. Стандарт содержит несколько решений этой проблемы: виртуальное наследование, виртуальные деструкторы и явное указание на базовый класс (в случае с шаблонами). О последнем и пойдет речь.
Считается, что С++ не поддерживает повторное наследование т.к. нельзя явно указать на используемый базовый класс (википедия это мнение разделяет). Буквальное повторное наследование в стиле:


struct A
{
};

struct B : A, A
{
};


Конечно, недопустимо и противоречит здравому смыслу. Косвенное повторное наследование в стиле:


struct A
{
};

struct B : A
{
};

struct C : B, A
{
};


Доступно (с предупреждением компилятора), но так же лишено смысла.

Но не для случая, когда базовый класс шаблонный. Самое интересное начинается, когда базовый класс содержит шаблонную логику, например, применимую к классам-потомкам. Так, ситуация, когда надо получить свой определяемый тип «умный указатель» Ptr для каждого класса-потомка, запутает компилятор:


#include <memory>
#include <iostream>
#include <typeinfo.h>

template<typename T>
struct CSmartPointer
{
    typedef std::tr1::shared_ptr<T> Ptr;
};

struct A : CSmartPointer<A>
{
};

struct B : A, CSmartPointer<B>
{
};

struct C : B, CSmartPointer<C>
{
};

void main()
{
    std::cout << typeid(C::Ptr).name() << std::endl;
}


Компилятор увидит явную неоднозначность — какой из трёх типов A::Ptr, B::Ptr или C::Ptr выбрать, и выдаст ошибку. Эта проблема многих вводит в ступор, часто источники предлагают переименовать части базовых классов и исказить базовую архитектуру. Тем не менее, стандарт поддерживает разрешение неоднозначности — явное указание нужного базового класса через директиву using. Например:


using CSmartPointer<B>::Ptr;


Так, если явно указать, какую ветвь наследования применить внутри конкретного класса-потомка:


#include <memory>
#include <iostream>
#include <typeinfo.h>

template<typename T>
struct CSmartPointer
{
    typedef std::tr1::shared_ptr<T> Ptr;
};

struct A : CSmartPointer<A>
{
};

struct B : A, CSmartPointer<B>
{
    using CSmartPointer<B>::Ptr;
};

struct C : B, CSmartPointer<C>
{
    using CSmartPointer<C>::Ptr;
};

void main()
{
    std::cout << typeid(A::Ptr).name() << std::endl;
    std::cout << typeid(B::Ptr).name() << std::endl;
    std::cout << typeid(C::Ptr).name() << std::endl;
}


То неоднозначность исчезнет, компилятор код скомпилирует, а в копилке будет еще одно универсальное решение.
Tags:
C++, множественное наследование, неоднозначность, шаблоны, using

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.