Указание разрешенных аргументов в шаблонах

Могу ли я точно указать, какие аргументы может принимать шаблон? Например, я хотел бы создать шаблон, который может быть создан только с классами, которые являются или расширяют класс A. В Java дженерики поддерживают это с помощью:

class B<T extends A> { }

Можно ли добиться чего-то подобного с помощью шаблонов в C++?

template <typename T (?)> class B { }

person rid    schedule 15.08.2012    source источник
comment
Вы можете сделать это через static_assert и std::is_same (для A) и std::is_base_of (для производных). Я напишу пример. Я думаю, что std::enable_if тоже мог бы это сделать, но я не могу сказать, что использовал его.   -  person chris    schedule 15.08.2012
comment
Вот пример. Вы можете изменить то, что было передано (a и b работают, а c — нет). Если у вас нет С++ 11 (для статического утверждения), я чувствую, что std::enable_if может помочь, но я не совсем уверен, как его использовать. Это также не даст четкого сообщения об ошибке.   -  person chris    schedule 15.08.2012
comment
@chris, похоже, у меня нет заголовочного файла type_traits. Где мне искать static_assert, std::is_base_of и std::enable_if?   -  person rid    schedule 15.08.2012
comment
Нет type_traits? Это странно, так как это стандартный заголовок. Это должно быть со всеми остальными. static_assert предназначен только для C++11, и на самом деле кажется, что enable_if тоже, хех. is_base_of тоже, поэтому я думаю, что для этого требуется С++ 11, потому что я не могу придумать другого гладкого способа сделать это. Чтобы использовать это, вам нужен компилятор с поддержкой этих функций и опция -std=c++11.   -  person chris    schedule 15.08.2012
comment
@chris, понятно... так это довольно недавнее дополнение?   -  person rid    schedule 15.08.2012
comment
Я обнаружил, что у boost есть собственные версии enable_if и is_base_of, которые можно использовать в C++03. Остальные являются частью C++ по состоянию на прошлый год, так что да, они довольно новые по сравнению с материалом C++03. Метапрограммирование действительно эволюционировало благодаря этому дополнению. Boost — это то, без чего почти не могут обойтись пользователи C++03, и пользователи C++11 тоже получают от этого пользу. Мне удалось просто проигнорировать его существование, в то время как стандартный C++ копирует их идеи.   -  person chris    schedule 15.08.2012
comment
@chris, насколько я понимаю, С++ 11 сильно вдохновлен повышением, поэтому вполне вероятно, что большинство функций С++ 11 на самом деле являются функциями повышения. Итак, если я хочу сохранить переносимость программы (поскольку поддержка С++ 11 в то время довольно плохая), мне нужно либо использовать ускорение, либо не поддерживать эту функциональность?   -  person rid    schedule 15.08.2012
comment
Возможно, мне не хватает другого, более хакерского способа сделать это, но я не думаю, что есть что-то еще, кроме С++ 11 или повышения.   -  person chris    schedule 15.08.2012


Ответы (2)


Есть два способа сделать это.

Во-первых, через скрытый фиктивный параметр шаблона, который использует std::enable_if с условием std::is_base_of<A, T>::value. Если последнее выражение оценивается как false, то вложенное type не существует в std::enable_if. Если вы использовали это для перегруженных функций, SFINAE означает, что «сбой замены не является ошибкой», и рассматриваемая перегрузка будет удалена из набора жизнеспособных функций. Однако в этой ситуации нет другого шаблона класса, соответствующего вашему вызову, и тогда вы получите ошибку времени компиляции.

SFINAE — очень тонкий механизм, и в нем легко ошибиться. Например. если у вас есть несколько специализаций классов с разными условиями SFINAE, вы должны убедиться, что все они не перекрываются, иначе вы получите двусмысленность.

Во-вторых, вы можете сделать простой static_assert с std::is_base_of<A,T>::value внутри тела класса. Преимущество этого метода заключается в том, что вы также указываете более читаемое сообщение об ошибке по сравнению с методом SFINAE. Недостатком является то, что вы всегда получаете ошибку, и вы не можете молча подавить этот конкретный шаблон и выбрать другой. Но в целом я думаю, что этот метод рекомендуется в вашем случае.

#include<type_traits>

class A {};
class C: public A {};
class D {};

// first alternative: SFINAE on hidden template parameter
template
<
    typename T, 
    typename /* dummy */ = typename std::enable_if< 
        std::is_base_of<A, T>::value
    >::type
>
class B
{
};

// second alternative: static_assert inside class
template
<
    typename T
>
class E
{
    static_assert(std::is_base_of<A, T>::value, "A should be a base of T");
};

int main()
{
    B<A> b1;
    B<C> c1;
    //B<D> d1; // uncomment this line to get a compile-time error

    E<A> b2;
    E<C> c2;
    //E<D> d2; // uncomment this line to get a compile-time error

    return 0;
}

Как было указано в комментариях, вы можете использовать либо достойный компилятор C++11 (VC++ 2010 или новее, gcc 4.5 или новее), либо библиотеки Boost или TR1, чтобы получить функциональность <type_traits>. Однако обратите внимание, что std::is_base_of<A, A>::value оценивается как true, а старый boost::is_base_of<A, A>::value раньше оценивался как false.

person TemplateRex    schedule 15.08.2012

Вы можете сделать это с помощью static_assert и is_base_of:

#include <type_traits>
template<typename T> class D {
    static_assert(std::is_base_of<A, T>::value, "must be derived from A");
};

Или вы можете использовать enable_if:

#include <type_traits>
template<typename T, typename = void> class D;
template<typename T> class D<T, typename std::enable_if<std::is_base_of<A, T>::value>::type> {
};

Для С++ 03 вы можете использовать boost; is_base_of из Boost.TypeTraits, static_assert из Boost.StaticAssert, enable_if из Boost.EnableIf.

person ecatmur    schedule 15.08.2012