Как специализировать класс свойств для T, а также для всех потомков T

Я хочу, чтобы класс свойств применялся к типу, а также к его потомкам. Это возможно?

template <typename E>
struct Garble {
};

template <typename T>
struct wooble_traits;

template <typename E>
struct wooble_traits<Garble<E>> {
  typedef E elem_type;
};

struct IntGarble : public Garble<int> {
};

typedef typename wooble_traits<IntGarble>::elem_type IGType;
//Error, wooble_traits<IntGarble> has no definition.

Есть ли способ вместо этого сказать (заимствование и злоупотребление нотацией Java):

template <typename E>
struct wooble_traits<? extends Garble<E>> {
  typedef E elem_type
};

typedef typename wooble_traits<IntGarble>::elem_type IGType;
//Fine, IGType is an alias for int

Примечание.
Попытка адаптировать решение Dyp к моему примеру не работает, поскольку Garble принимает параметр типа. Кажется, нигде нельзя вывести этот параметр.

#include <boost/type_traits/is_base_of.hpp>

template <typename T, typename C = void>
struct wooble_traits;

template <typename T, typename E>
struct wooble_traits<T, typename boost::is_base_of<Garble<E>, T>::type> {
  typedef E elem_type;
};

в gcc-4.6 это производит:

g++ -I/usr/include/boost/utility -I/usr/include/boost/type_traits -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"main.d" -MT"main.d" -o "main.o" "../main.cpp"
../main.cpp:15:8: error: template parameters not used in partial specialization:
../main.cpp:15:8: error:         ‘E’
make: *** [main.o] Error 1

что понятно, поскольку у GCC нет возможности узнать значение E.


person dspyz    schedule 24.03.2014    source источник
comment
@Джеффри: например, IntGarble. подкласс/производный класс.   -  person Karoly Horvath    schedule 25.03.2014
comment
Под потомком я подразумеваю производный класс. Класс свойств делает информацию о типе доступной во время компиляции. wooble_traits предоставляет elem_type wooble для использования во время компиляции. При просмотре как wooble Garble имеет elem_type такой же, как и его аргумент шаблона. Все производные классы от некоторого Garble (включая IntGarble) имеют тот же elem_type, что и их предок Garble (в случае IntGarble это тип int).   -  person dspyz    schedule 25.03.2014


Ответы (1)


Для базовых классов:

#include <type_traits>

template<bool b>
using stdbool_t = std::integral_constant<bool, b>;

template<class T, class U = std::true_type>
struct trait
    : std::false_type
{};

struct foo {};
struct bar : foo {};

template<class T>
struct trait<T, stdbool_t<std::is_base_of<foo, T>{}>>
    : std::true_type
{};


#include <iostream>

int main()
{
    std::cout << std::boolalpha;

    std::cout << trait<int>::value << "\n";
    std::cout << trait<foo>::value << "\n";
    std::cout << trait<bar>::value << "\n";
}

По какой-то причине специализация на параметрах шаблона, не относящихся к типу, не допускается, когда выражение зависит от (предыдущего) параметра типа.

g++4.8.2 не может скомпилировать этот код (ICE), но он отлично работает с clang+ +3,5


Вот альтернативная версия, которая компилируется на обоих компиляторах:

template<class T, class = void>
struct trait
    : std::false_type
{};

struct foo {};
struct bar : foo {};

template<class T>
struct trait<T, typename std::enable_if<std::is_base_of<foo, T>{}>::type>
    : std::true_type
{};

На самом деле enable_if не нужен. std::conditional тоже подойдет, но здесь enable_if короче.


Если база является специализацией шаблона, мы можем использовать Jarod42 решение (я его немного изменил):

template<template<class...> class T, class U>
struct is_base_template_of
{
private:
    template<class... V>
    static auto test(const T<V...>&)
        -> decltype(static_cast<const T<V...>&>(std::declval<U>()),
                    std::true_type{});

    static std::false_type test(...);

public:
    static constexpr bool value =
        decltype(is_base_template_of::test(std::declval<U>()))::value;
};


template<class T>
struct trait<T,
             typename std::enable_if<is_base_template_of<foo, T>::value>::type>
    : std::true_type
{};

Примечание: это работает только для публичного наследования (и я думаю, что у него есть некоторые дополнительные ограничения: виртуальное и некоторые случаи множественного наследования также не должны работать).


Вот версия трейта того же типа в стиле "C++03 + boost":

template<template<class> class T, class U>
struct is_base_template_of
{
private:
    typedef char false_type;
    typedef char(& true_type)[2];

    template<class V>
    static true_type test(const T<V>*);
    static false_type test(...);

public:
    static const bool value =
       (   sizeof(test(std::declval<typename std::remove_reference<U>::type*>()))
        == sizeof(true_type));
};
person dyp    schedule 24.03.2014
comment
Хорошо, но как насчет того, когда foo принимает параметр шаблона (как в моем примере). Как вы это делаете тогда? (не заставляя клиента знать тип этого параметра) - person dspyz; 25.03.2014
comment
@dspyz Ой, простите, я слишком упростил тестовый пример. Добавлено решение, в котором базовый класс является специализацией шаблона. - person dyp; 25.03.2014
comment
Хорошо, это выглядит хорошо, но проект, над которым я работаю, не использует C++11 (как вы могли догадаться из моих примеров на основе повышения). Есть ли способ сделать это без decltype? - person dspyz; 25.03.2014
comment
О, у boost есть макрос TYPEOF, который, похоже, делает то же самое. Я протестирую его, чтобы увидеть, работает ли он. - person dspyz; 25.03.2014
comment
@dspyz Часть decltype просто делает SFINAE немного удобнее. Попробую заменить на старомодный SFINAE. - person dyp; 25.03.2014
comment
@dspyz Попробуйте версию в стиле C++03 + boost - person dyp; 25.03.2014
comment
Я заменил std::declval‹typename std::remove_reference‹U›::type*› на (U*) 0. Он компилируется, но теперь я не могу понять, как получить параметр шаблона (как вы помните, я хотел сделать traits::elem_type псевдонимом для E (V в вашем примере)). Но при использовании is_base_template_of эта информация теряется. - person dspyz; 25.03.2014
comment
declval можно легко реализовать в C++03: template<class T> T declval(); (отличие от C++11 в том, что последний возвращает T&&). Получить параметр шаблона можно с помощью вывода, очень похожего на тот, который использовался в моем посте, я добавлю пример. - person dyp; 25.03.2014
comment
@dspyz boost также имеет declval. Пример не так прост, как я думал, так как я могу не использовать decltype :( Внутри функции test параметр шаблона известен, поэтому вы всегда можете написать аналогичную функцию, а внутри использовать параметр шаблона. Не уверен, что сможете вытащите его из из функции. - person dyp; 25.03.2014
comment
Ну, это стало гораздо более сложным и запутанным, чем я готов терпеть в любом случае. Я хотел сделать это без каких-либо изменений в существующих классах, но на данный момент это не стоит дополнительных усилий. Я просто собираюсь добавить typedef E elem_type непосредственно в сами классы (вместо того, чтобы иметь отдельный класс свойств). Они уже настолько раздуты ненужным кодом, что 2-3 лишние строчки могут даже остаться незамеченными. Я отмечу это как принятый ответ, поскольку, похоже, ни у кого больше нет других идей, и я многому научился из этого. - person dspyz; 26.03.2014