2015-07-23 3 views
5

Я хочу, чтобы определить шаблон функции:Функция шаблона, которая соответствует только определенным типам?

template<typename T> 
void foo(T arg) 

Но я хочу, чтобы соответствовать T только определенные типы. В частности, T должно выводиться (возможно, через множественное наследование), формировать определенный базовый класс. В противном случае этот шаблон не должен включаться в набор перегрузки.

Как это сделать?

+0

, связанные с (http://stackoverflow.com/ q/16976720/1708801) –

+0

@ShafikYaghmour Обратите внимание, что это не дубликат. Я хочу ограничить шаблон * function *, в то время как связанный вопрос относится к шаблону * class *. – becko

+0

@ShafikYaghmour Не могли бы вы закрыть его, если бы это был точный дубликат, и у вас был ответ на другой? – Barry

ответ

13

Использование SFINAE с std::is_base_of:

template <typename T, 
      typename = std::enable_if_t< 
       std::is_base_of<Foo, T>::value 
      >> 
void foo(T arg); 

Это будет включать в себя только foo в перегрузке, установленной, если T наследует от Foo. Обратите внимание, что это включает в себя двусмысленные и недоступные базы. Если вы хотите, решение, которое позволяет только T с наследуемых публично и однгозначно из Foo, то вы можете использовать вместо std::is_convertible:

template <typename T, 
      typename = std::enable_if_t< 
       std::is_convertible<T*, Foo*>::value 
      >> 
void foo(T arg); 

Обратите внимание на разворот аргументов.

Независимо от того, какая форма вам выбрать, он может быть совмещенным для краткости:

template <typename T> 
using enable_if_foo = std::enable_if_t<std::is_base_of<Foo, T>::value>; 

template <typename T, 
      typename = enable_if_foo<T>> 
void foo(T arg); 

Это работает, потому что std::enable_if имеет вложенный тип с именем type тогда и только тогда, когда логическое Передаваемый true. Так что, если std::is_base_of<Foo, T>::value является true, enable_if_t получает экземпляр в void, как если бы мы написали:

template <typename T, 
      typename = void> 
void foo(T arg); 

Но, если T не наследует от Foo, то тип черта будет оценивать, как false и std::enable_if_t<false> провал замещения - нет typename enable_if<false>::type. Можно ожидать, что это ошибка компиляции, но сек ubstitution е ailure я сек п ВЗ н е rror (SFINAE). Это просто отказ от шаблона. Таким образом, эффект заключается в том, что foo<T> просто удаляется из набора жизнеспособных кандидатов на перегрузку в этом случае, ничем не отличается от любого другого отказа вычета шаблонов.

+0

Я не нахожу 'std :: enable_if_t'. Должно ли это быть 'std :: enable_if'? – becko

+1

'template using enable_if_t = typename std :: enable_if :: type;' является однострочной реализацией 'enable_if_t'. Вставьте его в 'namespace notstd', и когда ваш компилятор обновит, переключите' notstd :: enable_if_t' на 'std :: enable_if_t'.Псевдоним стоит дополнительный бит шаблона, синтаксис 'enable_if' без него раздражает. – Yakk

+0

Это то, что я хочу, спасибо (+1). Не возражаете ли вы объяснить, по крайней мере, на поверхности, как это работает? – becko

5

Методы на основе SFINAE, такие как:

template <typename T, 
    typename Test = std::enable_if_t<std::is_base_of<Foo, T>::value>> 
void foo(T arg); 

Хорошо удалить функцию из списка перегрузки - что будет общим случаем.

Если вы хотите сохранить функцию в списке и если выбрана лучшая перегрузка для отказа, если тип соответствует некоторым критериям (например, базовое требование здесь), можно использовать static_assert;

template <typename T> 
void foo(T arg) 
{ 
    static_assert(std::is_base_of<Foo, T>::value, "failed type check"); 
    // ... 
} 
3

В C++ 1Z с понятиями облегченный, вы можете сделать это:

template<class T> 
requires std::is_base_of<Foo, T>{}() 
void foo(T arg) { 
} 

под током (экспериментальной) реализации. Это довольно чисто и понятно. Там может быть способ сделать что-то вроде:

template<derived_from<Foo> T> 
void foo(T arg) { 
} 

, но я не работал его. Вы можете определенно сделать:

template<derived_from_foo T> 
void foo(T arg){ 
} 

, где у нас есть собственный концепт под названием derived_from_foo, которая применяется тогда и только тогда тип является производным от foo. То, что я не знаю, как это сделать, - это концепции шаблонов - концепции, созданные из параметров типа шаблона.

В C++ 14, здесь два метода. Во-первых, нормальный SFINAE:

template<class T, 
    class=std::enable_if_t<std::is_base_of<Foo, T>{}> 
> 
void foo(T arg) { 
} 

здесь мы создаем шаблон, который выводит тип T из своего аргумента. Затем он пытается вывести свой второй аргумент типа из первого аргумента.

Аргумент второго типа не имеет имени (следовательно, class=), потому что мы используем его только для теста SFINAE.

Испытание enable_if_t<condition>. enable_if_t<condition> генерирует тип void, если верно condition. Если condition является ложным, он не работает в «непосредственном контексте», генерируя сбой замены.

SFINAE - «Ошибка замены» не является ошибкой »- если ваш тип T генерирует сбой в« непосредственном контексте »сигнатуры шаблона функции, это не генерирует ошибку времени компиляции, а приводит к в этом случае шаблон функции не считается допустимой перегрузкой.

«Непосредственный контекст» является техническим термином здесь, но в основном это означает, что ошибка должна быть «достаточно ранней», чтобы ее можно было поймать. Если это требует компиляции тел функций, чтобы найти ошибку, то это не в «непосредственном контексте».

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

template<class T> 
struct tag { 
    using type=T; 
    constexpr tag(tag const&) = default; 
    constexpr tag() = default; 
    template<class U, 
    class=std::enable_if_t<std::is_base_of<T,U>{}> 
    > 
    constexpr tag(tag<U>) {} 
}; 

struct Base{}; 
struct Derived:Base{}; 

template<class T> 
void foo(T t, tag<Base> = tag<T>{}) { 
} 

здесь мы создаем тип tag отправки, и это позволяет преобразовать в базу. tag позволяет нам использовать типы как значения и использовать для них более обычные операции на C++ (вместо шаблонного метапрограммирования <> по всему месту).

Затем мы даем foo второй аргумент типа tag<Base>, затем строим его с помощью tag<T>. Это невозможно скомпилировать, если T не является производным типом от Base.

live example.

Приятная вещь об этом решении заключается в том, что код, который делает его неработоспособным, кажется более интуитивно понятным - tag<Unrelated> не может преобразовать в tag<Base>. Это, однако, не мешает рассмотрению функции для разрешения перегрузки, что может быть проблемой.

Путь с меньшей котельной пластиной:

template<class T> 
void foo(T t, Base*=(T*)0) { 
} 

где мы используем тот факт, что указатели могут быть преобразованы тогда и только тогда существует вывод отношение между ними.


В C++ 11 (и без поддержки constexpr), мы сначала написать помощника:

namespace notstd { 
    template<bool b, class T=void> 
    using enable_if_t=typename std::enable_if<b,T>::type; 
} 

затем:

template<class T, 
    class=notstd::enable_if_t<std::is_base_of<Foo, T>::value> 
> 
void foo(T arg) { 
} 

, если вам не нравится, помощник, мы получаем этот уродливый дополнительный:

template<class T, 
    class=typename std::enable_if<std::is_base_of<Foo, T>::value>::type 
> 
void foo(T arg) { 
} 

t второй метод C++ 14 выше также можно перевести на C++ 11.


Вы можете написать псевдоним, который делает тест, если вы хотите: [? Как ограничить класс шаблонов для определенных типов]

template<class U> 
using base_test=notstd::enable_if_t<std::is_base_of<Base, U>::value>; 

template<class T, 
    class=base_test<T> 
> 
void foo(T arg) { 
} 
+0

+1 Отличный ответ, но, пожалуйста, см. Мой последний комментарий к ответу Барри. Есть ли сокращение, чтобы избежать написания одного и того же шаблона много раз? Я использую CLion, и я думаю, что он не полностью понимает C++ 14, поэтому я застрял с C++ 11. – becko

+0

@becko short-form alias добавлен. Обратите внимание, однако, что решение 'tag' довольно короткое. – Yakk

+0

«блеск респектабельности», * snort *. ваши [как обычно] ответы всегда меня взламывают. – Barry

Смежные вопросы