Я бы вернулся к первому решению, которое вы предложили в исходном вопросе (то, которое предполагает, что каждый шаблон не листового класса объявляет тип DerivedT
, который псевдоним его производного класса).Давайте вспомним вашу первую реализацию:
template <class T>
struct GetDeepest {
template <class Test, class = typename Test::DerivedT>
static std::true_type Helper(const Test&);
static std::false_type Helper(...);
using HelperType = decltype(Helper(std::declval<T>()));
using Type = std::conditional_t<std::is_same_v<std::true_type, HelperType>,
GetDeepest<typename T::DerivedT>::Type,
T>;
};
Кажется, вам не хватает того, что листовые классы также наследуют объявление типа члена DerivedT
от своего базового класса.Это означает, что вызов Helper(std::declval<D>())
вызовет перегрузку, которая возвращает std::true_type
.Другими словами:
template <typename T>
struct A {
using DerivedT = T;
};
struct D : A<D> {
using DerivedT = D; // inherited from A<D>
};
В результате, когда создается экземпляр GetDeepest<D>
, GetDeepest<D>::Type
заканчивает псевдонимом GetDeepest<D::DerivedT>::Type
, равным GetDeepest<D>::Type
, и компилятор жалуется на то, что GetDeepest<D>::Type
не является полным типомпоскольку он пытается быть псевдонимом для себя!
Поэтому нам нужно изменить условие остановки рекурсии.Одно из решений, которое приходит на ум:
Тип T
является конечным узлом в иерархии CRTP, если его тип DerivedT
является псевдонимом T
И реализация довольно проста:
#include <type_traits>
// a type T is a leaf-node in the CRTP hierarchy iff:
// its DerivedT member type is an alias to T
template <typename T>
inline constexpr bool is_leaf_type_v = std::is_same_v<T, typename T::DerivedT>;
// general case:
// GetDeepest<T>::Type is an alias to GetDeepest<T::DerivedT>::Type
template <typename T, typename = void>
struct GetDeepest {
using Type = typename GetDeepest<typename T::DerivedT>::Type;
};
// base case: when T is a leaf type
// We have reached a leaf node => GetDeepest<T>::Type is an alias to T
template <typename T>
struct GetDeepest<T, std::enable_if_t<is_leaf_type<T> > > {
using Type = T;
};
// tests
template <class T>
struct A {
using DerivedT = T;
};
template <class T>
struct B : public A<B<T> > {
using DerivedT = T;
};
struct C : B<C> {
};
struct D : A<D> {
};
int main()
{
static_assert(std::is_same<GetDeepest<A<D> >::Type, D>::value);
static_assert(std::is_same<GetDeepest<B<C> >::Type, C>::value);
static_assert(std::is_same<GetDeepest<A<B<C> > >::Type, C>::value);
}
Этот подход не требует от вас указывать аргументы шаблона для вашего класса в GetDeepest
(что является основной проблемой в ваших 2-х вопросах, если яправильно понимаю).Я думаю, что вы также можете реализовать FindFirstMathing
аналогичным образом.
Я бы также не стал зависеть от IsTemplateInstantiation
, чтобы выяснить, является ли тип листовым в иерархии CRTP или нет (дажеесли это возможно реализовать).Один встречный пример, который приходит на ум, это Eigen::Matrix<int, 3, 3>
, который представляет собой шаблон класса CRTP листового типа, который наследуется от Eigen::PlainObjectBase<Eigen::Matrix<int, 3, 3>>
.Такое различие между классами и экземплярами шаблонов классов не является тем, что вы ищете.