Обход цепочки наследования CRTP для базовых классов шаблона с нетиповыми параметрами шаблона - PullRequest
2 голосов
/ 12 июня 2019

Это продолжение вопроса: Получить самый глубокий класс в цепочке наследования CRTP

Следующий код находит первый аргумент шаблона, который получается из данного экземпляра базового класса CRTP шаблона и, возможно, рекурсивно (это просто более общее решение предыдущего вопроса):

// Find the first type that matches the predicate
template <template <class T> class Predicate, class... Args>
struct FindFirstMatching {
    using Type = ...; // Default NullType
    static const bool has_match = ...; // whether there has been a match
};

// Utility class to get the deepest class in CRTP inheritance chain
template <typename T>
struct GetDeepest {
    using Type = T;
};

template <template <class...> class DT, class... T>
struct GetDeepest<DT<T...>> {
    template <class CLS>
    struct Predicate {
        static const bool value = std::is_base_of_v<DT<T...>, CLS>;
    };

    static const bool HasCRTPDerived = FindFirstMatching<Predicate, T...>::has_match;
    using DerivedT = typename FindFirstMatching<Predicate, T...>::Type;

    using Type = std::conditional_t<HasCRTPDerived, 
                                    typename GetDeepest<DerivedT>::Type, 
                                    DT<T...>>;
};

Итак, я полагаю, что DT<T...> находится в реализации базового класса CRTP. Проблема в том, что у него могут быть и нетипичные параметры шаблона, и я не знаю, как с ним справиться самым общим способом.

Например:

template <class DerivedT, bool param>
class ParamBase {
};

class Derived : public ParamBase<Derived, false> {
};

GetDeepest<ParamBase<Derived, false>>::Type == ParamBase<Derived, false>
// instead of desirable
// GetDeepest<ParamBase<Derived, false>>::Type == Derived

Одним из возможных решений является использование чего-то вроде тегов типа вместо нетипизированных параметров шаблона:

template <class T>
A {};

A<TrueType>;
A<FalseType>;

вместо

template <bool param>
A{};

Но я считаю, что это не очень хорошее решение.

Также я мог бы использовать что-то вроде template <class..., auto...> DT, но это заставило бы меня использовать нетипичные параметры шаблона в конце списка параметров. Это приемлемо, но проблема в том, что он потерпит катастрофический крах, если я забуду об этом.

Было бы хорошо, если бы я мог написать что-то вроде этого:

template <typename T>
struct GetDeepest {
    static_assert(!IsTemplateInstantiation<T>::value);
    using Type = T;
};

Мой вопрос:

Как я могу обобщить код на возможные нетиповые параметры шаблона?

OR

Как я могу реализовать IsTemplateInstantiantion?

Edit: похоже, что никто не может написать template <class..., auto...> в C ++ 17, поэтому последнее не вариант.

1 Ответ

1 голос
/ 13 июня 2019

Я бы вернулся к первому решению, которое вы предложили в исходном вопросе (то, которое предполагает, что каждый шаблон не листового класса объявляет тип 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>>.Такое различие между классами и экземплярами шаблонов классов не является тем, что вы ищете.

...