Scott Meyer发布了他的下一本书EC++11的content and status,他在书中写道,书中的一条可能是“在函数签名中避免std::enable_if
”。
std::enable_if
可以用作函数参数、返回类型或类模板或函数模板参数,以便有条件地从重载解析中删除函数或类。
在this question中,显示了所有三种解决方案。
作为函数参数:
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;
}
};
std::enable_if
”涉及到作为返回类型的使用(这不是普通函数签名的一部分,而是模板specializations)?发布于 2013-01-31 18:42:28
将hack放在模板参数中。
与其他方法相比,enable_if
on template参数方法至少有两个优点:
对我来说,可读性是这个选择的主要动机因素。
发布于 2013-01-30 17:33:52
std::enable_if
在模板参数演绎过程中依赖于"“(又名SFINAE)原则。这是一个非常脆弱的语言特性,您需要非常小心才能正确使用它。
enable_if
中的条件包含嵌套模板或类型定义(提示:查找::
标记),则这些嵌套模板或类型的解析通常是非推导上下文。这种非推导上下文上的任何替换失败都是error.enable_if
重载中的各种条件不能有任何重叠,因为重载解析将是不明确的。这是你作为作者需要检查自己的东西,尽管你会得到好的编译器warnings.enable_if
在重载解析期间操纵一组可行的函数,这可能会有令人惊讶的交互,这取决于从其他作用域(例如,通过ADL)引入的其他函数的存在。这使得它不是很健壮。简而言之,当它工作时,它可以工作,但当它不工作时,它可能很难调试。一种非常好的替代方法是使用标记分派,即委托给一个实现函数(通常在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 */) {}
}
标签调度并不操作重载集,而是通过编译时表达式(例如,在类型特征中)提供适当的参数,帮助您准确地选择所需的函数。根据我的经验,这更容易调试和正确。如果您是一个具有复杂类型特征的有抱负的库编写者,那么您可能需要使用enable_if
,但是对于大多数经常使用的编译时条件,不推荐使用它。
发布于 2018-03-28 18:56:03
哪种解决方案应该是首选的,为什么我应该避免其他解决方案?
选项1:模板参数中的enable_if
template::value>> void f() {/*...*/} template::value>> void f() {/*...*/} //重新定义:两者都只是template f()
请注意,使用了typename = std::enable_if_t<cond>
而不是正确的std::enable_if_t<cond, int>::type = 0
选项2:返回类型中的enable_if
它不能与构造函数(没有返回类型)一起使用,也不能在用户定义的转换运算符中使用(因为它不是deducible)
选项3:函数参数中的enable_if
在用户定义转换运算符中不能使用函数签名(它们没有pre-C++11.
+
,-
,*
和其他。
void* = nullptr
);这会导致指向函数的指针行为不同,等等。成员函数模板和非成员函数模板有什么区别吗?
继承和using
之间有细微的区别
根据using-declarator
(强调我的观点):
使用声明程序引入的声明集是通过对使用声明程序中的名称执行限定名称查找(basic.lookup.qual,class.member.lookup)来找到的,但不包括如下所述的隐藏函数。..。当使用声明符将声明从基类带入派生类时,派生类中的成员函数和成员函数模板覆盖和/或隐藏基类中具有相同名称、参数类型列表、cv限定和引用限定符(如果有)的成员函数和成员函数模板(而不是冲突)。这样的隐藏或被覆盖的声明被排除在使用声明器引入的声明集之外。
因此,对于模板参数和返回类型,方法都被隐藏在以下场景中:
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找错了基函数)。
然而,对于参数,类似的情况是可行的:
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) {}
};
https://stackoverflow.com/questions/14600201
复制相似问题