C++ 为什么我应该避免在函数签名中使用 std::enable_if
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14600201/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why should I avoid std::enable_if in function signatures
提问by hansmaad
Scott Meyers posted content and statusof his next book EC++11.
He wrote that one item in the book could be "Avoid std::enable_if
in function signatures".
Scott Meyers 发布了他的下一本书 EC++11 的内容和状态。他写道,书中的一项可能是“避免std::enable_if
在函数签名中”。
std::enable_if
can be used as a function argument, as a return type or as a class template or function template parameter to conditionally remove functions or classes from overload resolution.
std::enable_if
可以用作函数参数、返回类型或类模板或函数模板参数,以有条件地从重载解析中删除函数或类。
In this questionall three solution are shown.
在这个问题中,显示了所有三个解决方案。
As function parameter:
作为函数参数:
template<typename T>
struct Check1
{
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, int>::value >::type* = 0) { return 42; }
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, double>::value >::type* = 0) { return 3.14; }
};
As template parameter:
作为模板参数:
template<typename T>
struct Check2
{
template<typename U = T, typename std::enable_if<
std::is_same<U, int>::value, int>::type = 0>
U read() { return 42; }
template<typename U = T, typename std::enable_if<
std::is_same<U, double>::value, int>::type = 0>
U read() { return 3.14; }
};
As return type:
作为返回类型:
template<typename T>
struct Check3
{
template<typename U = T>
typename std::enable_if<std::is_same<U, int>::value, U>::type read() {
return 42;
}
template<typename U = T>
typename std::enable_if<std::is_same<U, double>::value, U>::type read() {
return 3.14;
}
};
- Which solution should be preferred and why should I avoid others?
- In which cases "Avoid
std::enable_if
in function signatures"concerns usage as return type (which is not part of normal function signature but of template specializations)? - Are there any differences for member and non-member function templates?
- 应该首选哪种解决方案,为什么我应该避免其他解决方案?
- 在哪些情况下“避免
std::enable_if
在函数签名中”涉及作为返回类型的使用(这不是正常函数签名的一部分,而是模板特化的一部分)? - 成员函数模板和非成员函数模板有什么区别吗?
采纳答案by R. Martinho Fernandes
Put the hack in the template parameters.
将 hack 放入模板参数中。
The enable_if
on template parameter approach has at least two advantages over the others:
该enable_if
模板参数方法比别人至少有两个好处:
readability: the enable_if use and the return/argument types are not merged together into one messy chunk of typename disambiguators and nested type accesses; even though the clutter of the disambiguator and nested type can be mitigated with alias templates, that would still merge two unrelated things together. The enable_if use is related to the template parameters not to the return types. Having them in the template parameters means they are closer to what matters;
universal applicability: constructors don't have return types, and some operators cannot have extra arguments, so neither of the other two options can be applied everywhere. Putting enable_if in a template parameter works everywhere since you can only use SFINAE on templates anyway.
可读性:enable_if 的使用和返回/参数类型没有合并成一大块类型名称消歧器和嵌套类型访问;尽管可以使用别名模板减轻消歧器和嵌套类型的混乱,但这仍然会将两个不相关的东西合并在一起。enable_if 的使用与模板参数有关,而不是与返回类型有关。将它们放在模板参数中意味着它们更接近重要的东西;
普遍适用性:构造函数没有返回类型,有些运算符不能有额外的参数,所以其他两个选项都不能在任何地方应用。将 enable_if 放在模板参数中适用于任何地方,因为无论如何您只能在模板上使用 SFINAE。
For me, the readability aspect is the big motivating factor in this choice.
对我来说,可读性是这个选择的重要推动因素。
回答by TemplateRex
std::enable_if
relies on the "Substition Failure Is Not An Error" (aka SFINAE) principle during template argument deduction. This is a very fragilelanguage feature and you need to be very careful to get it right.
std::enable_if
在模板参数推导过程中依赖于“替换失败不是错误”(又名 SFINAE)原则。这是一个非常脆弱的语言功能,您需要非常小心才能正确使用。
- if your condition inside the
enable_if
contains a nested template or type definition (hint: look for::
tokens), then the resolution of these nested tempatles or types are usually a non-deduced context. Any substitution failure on such a non-deduced context is an error. - the various conditions in multiple
enable_if
overloads cannot have any overlap because overload resolution would be ambiguous. This is something that you as an author need to check yourself, although you'd get good compiler warnings. enable_if
manipulates the set of viable functions during overload resolution which can have surprising interactions depending on the presence of other functions that are brought in from other scopes (e.g. through ADL). This makes it not very robust.
- 如果您的条件中
enable_if
包含嵌套模板或类型定义(提示:查找::
标记),则这些嵌套模板或类型的解析通常是非推导的上下文。在这种非推导上下文中的任何替换失败都是错误。 - 多个
enable_if
重载中的各种条件不能有任何重叠,因为重载解析会不明确。这是您作为作者需要检查自己的事情,尽管您会收到很好的编译器警告。 enable_if
在重载解析期间操纵一组可行的函数,根据从其他范围(例如通过 ADL)引入的其他函数的存在,这些函数可能会产生令人惊讶的交互。这使得它不是很健壮。
In short, when it works it works, but when it doesn't it can be very hard to debug. A very good alternative is to use tag dispatching, i.e. to delegate to an implementation function (usually in a detail
namespace or in a helper class) that receives a dummy argument based on the same compile-time condition that you use in the enable_if
.
简而言之,当它工作时它可以工作,但当它不工作时,它可能很难调试。一个非常好的替代方法是使用标记分派,即委托给一个实现函数(通常在detail
命名空间或帮助器类中),该函数接收基于您在enable_if
.
template<typename T>
T fun(T arg)
{
return detail::fun(arg, typename some_template_trait<T>::type() );
}
namespace detail {
template<typename T>
fun(T arg, std::false_type /* dummy */) { }
template<typename T>
fun(T arg, std::true_type /* dummy */) {}
}
Tag dispatching does not manipulate the overload set, but helps you select exactly the function you want by providing the proper arguments through a compile-time expression (e.g. in a type trait). In my experience, this is much easier to debug and get right. If you are an aspiring library writer of sophisticated type traits, you might need enable_if
somehow, but for most regular use of compile-time conditions it's not recommended.
标签分派不操作重载集,而是通过编译时表达式(例如在类型特征中)提供正确的参数来帮助您准确选择所需的函数。根据我的经验,这更容易调试和正确处理。如果你是一个有抱负的复杂类型特征的库编写者,你可能需要enable_if
某种方式,但对于大多数经常使用的编译时条件,不建议这样做。
回答by Jarod42
Which solution should be preferred and why should I avoid others?
应该首选哪种解决方案,为什么我应该避免其他解决方案?
The template parameter
- It is usable in Constructors.
- It is usable in user-defined conversion operator.
- It requires C++11 or later.
- It is IMO, the more readable.
It might easily be used wrongly and produces errors with overloads:
template<typename T, typename = std::enable_if_t<std::is_same<T, int>::value>> void f() {/*...*/} template<typename T, typename = std::enable_if_t<std::is_same<T, float>::value>> void f() {/*...*/} // Redefinition: both are just template<typename, typename> f()
Notice
typename = std::enable_if_t<cond>
instead of correctstd::enable_if_t<cond, int>::type = 0
return type:
- It cannot be used in constructor. (no return type)
- It cannot be used in user-defined conversion operator. (not deducible)
- It can be use pre-C++11.
- Second more readable IMO.
Last, in function parameter:
- It can be use pre-C++11.
- It is usable in Constructors.
- It cannot be used in user-defined conversion operator. (no parameters)
- It cannot be used in methods with fixed number of arguments (unary/binary operators
+
,-
,*
, ...) - It can safely be use in inheritance (see below).
- Change function signature (you have basically an extra as last argument
void* = nullptr
) (so function pointer would differ, and so on)
模板参数
- 它可以在构造函数中使用。
- 它可用于用户定义的转换运算符。
- 它需要 C++11 或更高版本。
- 它是 IMO,更具可读性。
它可能很容易被错误使用并产生过载错误:
template<typename T, typename = std::enable_if_t<std::is_same<T, int>::value>> void f() {/*...*/} template<typename T, typename = std::enable_if_t<std::is_same<T, float>::value>> void f() {/*...*/} // Redefinition: both are just template<typename, typename> f()
注意
typename = std::enable_if_t<cond>
而不是正确std::enable_if_t<cond, int>::type = 0
返回类型:
- 它不能在构造函数中使用。(无返回类型)
- 它不能用于用户定义的转换运算符。(不可推算)
- 它可以使用 C++11 之前的版本。
- 第二个更具可读性的 IMO。
最后,在函数参数中:
- 它可以使用 C++11 之前的版本。
- 它可以在构造函数中使用。
- 它不能用于用户定义的转换运算符。(无参数)
- 它不能用于具有固定数量参数的方法(一元/二元运算符
+
,-
,*
, ...) - 它可以安全地用于继承(见下文)。
- 更改函数签名(您基本上有一个额外的作为最后一个参数
void* = nullptr
)(因此函数指针会有所不同,依此类推)
Are there any differences for member and non-member function templates?
成员函数模板和非成员函数模板有什么区别吗?
There are subtle differences with inheritance and using
:
继承和 之间存在细微差别using
:
According to the using-declarator
(emphasis mine):
根据using-declarator
(强调我的):
The set of declarations introduced by the using-declarator is found by performing qualified name lookup ([basic.lookup.qual], [class.member.lookup]) for the name in the using-declarator, excluding functions that are hidden as described below.
...
When a using-declarator brings declarations from a base class into a derived class, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list, cv-qualification, and ref-qualifier (if any) in a base class(rather than conflicting). Such hidden or overridden declarations are excluded from the set of declarations introduced by the using-declarator.
using-declarator 引入的一组声明是通过对 using-declarator 中的名称执行限定名称查找 ([basic.lookup.qual], [class.member.lookup]) 来找到的,不包括如所述隐藏的函数以下。
...
当 using-declarator 将基类的声明引入派生类时,派生类中的成员函数和成员函数模板将覆盖和/或隐藏具有相同名称、参数类型列表、cv-基类中的限定符和 ref 限定符(如果有)(而不是冲突)。这种隐藏或覆盖的声明被排除在 using-declarator 引入的声明集中。
So for both template argument and return type, methods are hidden is following scenario:
因此,对于模板参数和返回类型,隐藏方法的情况如下:
struct Base
{
template <std::size_t I, std::enable_if_t<I == 0>* = nullptr>
void f() {}
template <std::size_t I>
std::enable_if_t<I == 0> g() {}
};
struct S : Base
{
using Base::f; // Useless, f<0> is still hidden
using Base::g; // Useless, g<0> is still hidden
template <std::size_t I, std::enable_if_t<I == 1>* = nullptr>
void f() {}
template <std::size_t I>
std::enable_if_t<I == 1> g() {}
};
Demo(gcc wrongly finds the base function).
演示(gcc 错误地找到了基函数)。
Whereas with argument, similar scenario works:
而有论点,类似的场景有效:
struct Base
{
template <std::size_t I>
void h(std::enable_if_t<I == 0>* = nullptr) {}
};
struct S : Base
{
using Base::h; // Base::h<0> is visible
template <std::size_t I>
void h(std::enable_if_t<I == 1>* = nullptr) {}
};