我想定义一个功能模板:
T
但我希望T
仅匹配某些类型。具体来说,{{1}}应该派生(可能通过多重继承)形成某个基类。否则,此模板不应包含在重载集中。
我该怎么做?
答案 0 :(得分: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>
是替换失败 - 没有{{ 1}}。你可能会认为这是一个编译错误,但 s ubstitution f ailure i s n ot a n e rror(sfinae)。它只是模板演绎失败。因此,效果是在这种情况下,typename enable_if<false>::type
只是从可行的重载候选集中删除,与任何其他模板推导失败没有区别。
答案 1 :(得分: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");
// ...
}
答案 2 :(得分:4)
在带有concepts lite的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
为false,则在“直接上下文”中失败,从而导致替换失败。
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
调度类型,它允许转换为base。 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) {
}