Scott Meyers发布了他的下一本书EC 11的内容和状态。他写道,书中的一项可以是“在函数签名中避免std::enable_if
”。
如果可以用作函数参数、返回类型或类模板或函数模板参数,则可以有条件地从重载解析中删除函数或类。
在这个问题中,显示了所有三种解决方案。
作为功能参数:
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; }
};
作为模板参数:
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; }
};
作为返回类型:
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;
}
};
哪种解决方案应该是首选的,为什么我要避免其他解决方案?
>
它可用于用户定义的转换运算符。
它需要C 11或更高版本。
在我看来,它更具可读性(C 20之前)。
重载很容易被误用并产生错误:
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\u if\u t
它不能与构造函数(没有返回类型)一起使用
- 它不能在用户定义的转换运算符中使用(因为它是不可推导的)
- 它可以在C 11之前使用。
- 第二个更具可读性的IMO(预C 20)。
可以使用pre-C 11
它在构造函数中可用
它不能用于用户定义的转换运算符(它们没有参数)
- 它不能用于具有固定数量参数的方法中,例如一元/二元运算符、一元/二元运算符、一元/二元运算符、一元/二元运算符、一元/二元运算符、一元/二元/二元运算符、一元/二元/二元运算符、一元/二元/二元运算符、一元/二元/二元运算符、一元/二元运算符、二元/二元
- 在继承中使用它是安全的(见下文)
- 更改函数签名(您基本上有一个额外的as-last参数void*=nullptr);这会导致指向函数的指针行为不同等等
现在有
要求
子句
>
它在构造函数中可用
它可用于用户定义的转换运算符。
它需要C 20
IMO,最具可读性
与继承一起使用是安全的(见下文)。
可以直接使用类的模板参数
template <typename T>
struct Check4
{
T read() requires(std::is_same<T, int>::value) { return 42; }
T read() requires(std::is_same<T, double>::value) { return 3.14; }
};
成员和非成员函数模板有什么不同吗?
继承和使用
的有细微的区别:
根据使用声明符(emphasis mine)的
:
namespace.udecl
通过对using声明符中的名称执行限定名称查找([basic.lookup.qual]、[class.member.lookup]),可以找到using声明符引入的声明集,不包括如下所述隐藏的函数。
...
当使用声明符将基类中的声明引入派生类时,派生类中的成员函数和成员函数模板会覆盖和/或隐藏基类中具有相同名称、参数类型列表、cv限定和ref限定符(如果有)的成员函数和成员函数模板(而不是冲突)。此类隐藏或覆盖的声明被排除在使用声明符引入的声明集中。
因此,对于模板参数和返回类型,隐藏方法的场景如下:
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() {}
};
演示(gcc错误地找到了基函数)。
然而,对于参数,类似的场景也适用:
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) {}
};
演示
使用
也需要:
struct Base
{
template <std::size_t I>
void f() requires(I == 0) { std::cout << "Base f 0\n";}
};
struct S : Base
{
using Base::f;
template <std::size_t I>
void f() requires(I == 1) {}
};
演示
如果在模板参数推导过程中依赖“子项失败不是错误”(也称为SFINAE)原则,则启用。这是一个非常脆弱的语言特性,您需要非常小心才能正确使用它。
enable_if
中的条件包含嵌套模板或类型定义(提示:查找::
标记),那么这些嵌套模板或类型的解析通常是一个非推导上下文。在这样的非推导上下文上的任何替换失败都是错误的。enable_if
重载中的各种条件不能有任何重叠,因为重载解析将是模棱两可的。作为作者,您需要检查自己,尽管您会收到很好的编译器警告。enable_if
在重载解析期间操作一组可行的函数,这可能会有令人惊讶的交互,具体取决于从其他范围(例如通过ADL)引入的其他函数的存在。这使得它不是很健壮。简而言之,当它工作时,它工作,但当它不工作时,调试可能非常困难。一个很好的替代方法是使用标记调度,即委托给实现函数(通常在详细名称空间或帮助器类中),该函数基于您在enable\U 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 */) {}
}
标记调度不会操作重载集,但通过编译时表达式(例如在类型特征中)提供适当的参数,可以帮助您准确地选择所需的函数。根据我的经验,这更容易调试和正确。如果您是一位有抱负的具有复杂类型特征的库编写者,您可能需要在某种程度上启用If,但对于大多数编译时条件的常规使用,不建议这样做。
在模板参数中加入黑客攻击。
模板参数方法的enable_if
至少比其他方法有两个优点:
>
易读性:enable_if使用和返回/参数类型没有合并到一个混乱的类型名消歧器和嵌套类型访问块中;即使消歧器和嵌套类型的混乱可以通过别名模板来缓解,但这仍然会将两个不相关的东西合并在一起。enable_if使用与模板参数无关,与返回类型无关。模板参数中包含它们意味着它们更接近重要的东西;
普遍适用性:构造函数没有返回类型,一些运算符不能有额外的参数,因此其他两个选项都不能在任何地方应用。在模板参数中放置enable_if在任何地方都有效,因为无论如何只能在模板上使用SFINAE。
对我来说,可读性是这一选择的主要动机。
问题内容: 通常我会尽可能避免转换类型,因为我认为这是不良的编码实践,并且可能会导致性能下降。 但是,如果有人要我解释为什么会这样,我可能会像前灯中的鹿一样看它们。 那么,为什么/何时铸造不好? 它对于Java,C#,C ++是通用的,还是每个不同的运行时环境都按照自己的方式处理? 欢迎使用任何语言的细节,例如为什么在c ++中不好? 问题答案: 您已经用三种语言标记了这三种语言,答案在三种语言之
在React中,我尝试了两种方法: 然后更改状态this.setState(this.state) 克隆状态,更改状态克隆,然后更改此.setState(stateClone) 它们都起作用,产生相同的结果。为什么建议(在文档中)设置为状态克隆(使用Object.assign),而不是设置为状态本身?状态的对象标识在React中重要吗(没有Redux)?似乎只要调用setState,不管状态对象标
我正在创建一个带有片段的应用程序,在其中一个片段中,我创建了一个非默认构造函数,并收到以下警告: 有人能告诉我为什么这不是个好主意吗? 你能否建议我如何做到这一点: 不使用非默认构造函数?
问题内容: 我已经在多个地方多次看到过这种情况,但是从未找到令人满意的解释来说明为什么会这样。 因此,希望这里会介绍一个。为什么我们(至少通常)不使用和? 编辑:我看到人们以为这个问题与Web服务器有关,但事实并非如此。我可以理解为什么传递给未经处理的字符串可能很糟糕。在非Web应用程序中不好吗? 问题答案: 通常有更清晰,更直接的方法来获得相同的效果。如果构建复杂的字符串并将其传递给,则代码将难
问题内容: Process p = Runtime.getRuntime().exec(command); is = p.getInputStream(); byte[] userbytes = new byte[1024]; is.read(userbytes); 我想从java在linux os中执行shell命令。但是Pmd报告说不要使用Java Runtime.exec()。为什么?是什么
在下面的代码中,为什么允许绑定到函数? 演示 为什么允许这样做的直觉是什么,如何实现它,以及当您向传递一个lvalue参数,然后它调用接受rvalue引用的函数时,它实际上意味着什么?