В С++ 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
. Чего я не знаю, как делать, так это шаблонных понятий — понятий, сгенерированных из параметров типа шаблона.
В С++ 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
.
живой пример.
Прелесть этого решения в том, что код, из-за которого оно не работает, кажется более интуитивным — 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) {
}
второй описанный выше метод 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) {
}
person
Yakk - Adam Nevraumont
schedule
23.07.2015