仅匹配某些类型的模板函数?
Template function that matches only certain types?
我要定义函数模板:
template<typename T>
void foo(T arg)
但我希望 T
只匹配某些类型。具体来说,T
应该从某个基础 class 派生(可能通过多重继承)。否则此模板不应包含在重载集中。
我该怎么做?
将 SFINAE 与 std::is_base_of
一起使用:
template <typename T,
typename = std::enable_if_t<
std::is_base_of<Foo, T>::value
>>
void foo(T arg);
如果 T
继承自 Foo
,则仅在重载集中包含 foo
。请注意,这也包括模棱两可和不可访问的碱基。如果你想要一个只允许 T
s 从 Foo
继承 publicly 和 unambiguously 的解决方案,那么你可以而是使用 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
。您可能认为这是一个编译错误,但是 ssubstitution failure is not an e错误 (sfinae)。这只是模板推导失败。因此,在这种情况下,效果是 foo<T>
只是从可行的重载候选集中删除,与任何其他模板推导失败没有区别。
基于 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");
// ...
}
在精简概念的 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
。然后它会尝试从第一个参数中推断出它的 second 类型参数。
第二个类型参数没有名称(因此 class=
),因为我们仅将其用于 SFINAE 测试。
测试是enable_if_t< condition >
。如果 condition
为真,enable_if_t< condition >
生成类型 void
。如果 condition
为假,则在 "the immediate context" 中失败,生成替换失败。
SFINAE 是 "Substitution failure is not an error"——如果你的类型 T
在函数模板签名的 "immediate context" 中生成失败,这不会生成编译时错误,但是相反导致函数模板在这种情况下不被视为有效重载。
"Immediate context" 在这里是一个技术术语,但基本上它意味着错误必须是 "early enough" 才能被捕获。如果它需要编译函数体来查找错误,那不在 "the immediate context".
现在,这不是唯一的方法。我个人喜欢把我的 SFINAE 代码藏在体面的光环后面。下面,我使用标签分派 "hide" 其他地方的失败,而不是将其放在函数签名的最前面:
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
dispatch 类型,它允许转换为 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) {
}
我要定义函数模板:
template<typename T>
void foo(T arg)
但我希望 T
只匹配某些类型。具体来说,T
应该从某个基础 class 派生(可能通过多重继承)。否则此模板不应包含在重载集中。
我该怎么做?
将 SFINAE 与 std::is_base_of
一起使用:
template <typename T,
typename = std::enable_if_t<
std::is_base_of<Foo, T>::value
>>
void foo(T arg);
如果 T
继承自 Foo
,则仅在重载集中包含 foo
。请注意,这也包括模棱两可和不可访问的碱基。如果你想要一个只允许 T
s 从 Foo
继承 publicly 和 unambiguously 的解决方案,那么你可以而是使用 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
。您可能认为这是一个编译错误,但是 ssubstitution failure is not an e错误 (sfinae)。这只是模板推导失败。因此,在这种情况下,效果是 foo<T>
只是从可行的重载候选集中删除,与任何其他模板推导失败没有区别。
基于 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");
// ...
}
在精简概念的 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
。然后它会尝试从第一个参数中推断出它的 second 类型参数。
第二个类型参数没有名称(因此 class=
),因为我们仅将其用于 SFINAE 测试。
测试是enable_if_t< condition >
。如果 condition
为真,enable_if_t< condition >
生成类型 void
。如果 condition
为假,则在 "the immediate context" 中失败,生成替换失败。
SFINAE 是 "Substitution failure is not an error"——如果你的类型 T
在函数模板签名的 "immediate context" 中生成失败,这不会生成编译时错误,但是相反导致函数模板在这种情况下不被视为有效重载。
"Immediate context" 在这里是一个技术术语,但基本上它意味着错误必须是 "early enough" 才能被捕获。如果它需要编译函数体来查找错误,那不在 "the immediate context".
现在,这不是唯一的方法。我个人喜欢把我的 SFINAE 代码藏在体面的光环后面。下面,我使用标签分派 "hide" 其他地方的失败,而不是将其放在函数签名的最前面:
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
dispatch 类型,它允许转换为 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) {
}