在 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 >。如果condition 为真,enable_if_t< condition > 将生成void 类型。如果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++ 操作(而不是到处使用类似模板的元编程 <>s)。
然后我们给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) {
}
上面的第二种 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) {
}