为什么在没有参数的情况下调用省略号比可变参数模板更可取?
Why is an ellipsis preferred to a variadic template when called with no arguments?
我使用以下SFINAE模式来计算可变类型列表上的谓词:
#include <type_traits>
void f(int = 0); // for example
template<typename... T,
typename = decltype(f(std::declval<T>()...))>
std::true_type check(T &&...);
std::false_type check(...);
template<typename... T> using Predicate = decltype(check(std::declval<T>()...));
static_assert(!Predicate<int, int>::value, "!!");
static_assert( Predicate<int>::value, "!!");
static_assert( Predicate<>::value, "!!"); // fails
int main() {
}
令我惊讶的是,当使用空参数列表调用check
时选择了省号过载,因此即使sfinae表达式有效,Predicate<>
也是std::false_type
!
有什么变通办法吗?
当T...
为空时,编译器执行重载解析以确定
std::true_type check(); // instantiated from the function template
std::false_type check(...);
是最可行的候选者,如[over.match]中所述。13.3.3/1(引用N3936):
定义ICSi(F)如下:
如果F是一个静态成员函数,ICS1 (F)被定义为对于任何函数G, ICS1 (F)既不优于也不劣于ICS1 (G),并且,对称地,ICS1 (G)既不优于也不劣于ICS1 (F)132;否则,
令ICSi(F)表示将列表中的第i个实参转换为可行函数F的第i个形参类型的隐式转换序列,13.3.3.1定义了隐式转换序列,13.3.3.2定义了一个隐式转换序列比另一个更好或更差的转换序列的含义。
给定这些定义,如果对于所有参数i, ICSi(F1)不是比ICSi(F2)更差的转换序列,则可行函数F1被定义为比另一个可行函数F2更好的函数,然后
对于某些参数j, ICSj(F1)是比ICSj(F2)更好的转换序列,或者,如果不是,
上下文是用户自定义转换的初始化(参见8.5、13.3.1.5和13.3.1.6),从F1返回类型到目的类型(即被初始化实体的类型)的标准转换序列比从F2返回类型到目的类型的标准转换序列更好。(例子:
struct A { A(); operator int(); operator double(); } a; int i = a; // a.operator int() followed by no conversion // is better than a.operator double() followed by // a conversion to int float x = a; // ambiguous: both possibilities require conversions, // and neither is better than the other
- ]或者结束,如果不是,
上下文是对函数类型的引用进行直接引用绑定(13.3.1.6)的转换函数初始化,F1的返回类型与被初始化的引用类型相同(即左值或右值),F2的返回类型不是[示例:
template <class T> struct A { operator T&(); // #1 operator T&&(); // #2 }; typedef int Fn(); A<Fn> a; Fn& lf = a; // calls #1 Fn&& rf = a; // calls #2
- ]或者结束,如果不是,
F1不是函数模板专门化,F2是函数模板专门化,或者,如果不是,
- F1和F2是函数模板专门化,根据14.5.6.2中描述的偏序规则,F1的函数模板比F2的模板更专门化。
在本例中,由于没有参数,两个候选对象的转换序列都为空。倒数第二项是决定因素:
- F1不是函数模板专门化,F2是函数模板专门化,或者,如果不是,
因此首选非模板std::false_type check(...);
。
我更喜欢的解决方法——显然有很多——是制作两个候选模板,并通过省略号转换[over.ics]进行区分。省略号]13.3.3.1.3/1:
当函数调用中的实参与被调用函数的省略号形参规范匹配时(参见5.2.2),就会发生省略号转换序列。
通过为"preferred"模板声明提供一个明显更好匹配的外部参数,因为任何其他转换序列都将优于每个[over.ics]的省略号转换。排名]13.3.3.2/2:
当比较隐式转换序列的基本形式(如13.3.3.1中定义的)
- 标准转换序列(13.3.3.1.1)是比自定义转换序列或省略号转换序列更好的转换序列,
- 自定义转换序列(13.3.3.1.2)比省略号转换序列(13.3.3.1.3)更好。
的例子:
template<typename... T,
typename = decltype(f(std::declval<T>()...))>
std::true_type check(int);
template<typename...>
std::false_type check(...);
template<typename... T> using Predicate = decltype(check<T...>(0));
我也很惊讶。
一种解决方法是将int
(例如0
)作为check()
的第一个参数,并强制编译器首先尝试模板版本:
template<typename... T, typename = decltype(f(std::declval<T>()...))>
std::true_type check(int &&, T &&...); //ADDED `int &&` as the first parameter type
std::false_type check(...);
template<typename... T> using Predicate = decltype(check(0, std::declval<T>()...));
请注意,从0
创建的临时将首先尝试绑定到int&&
(这在这里非常关键),然后如果value-sfinae失败,那么它将尝试第二次重载。
- Demo @ Coliru
希望对你有帮助。
- 在不传递参数数量且只有3个点的情况下,如何使用变差函数
- 如何使用可变参数模板强制转换每个变体类型
- 关于如何在具有单个参数的变体构造中选择替代方案?
- 调用参数排列不变函数 f(i++, i++)
- 参数归纳与标准::变体
- 模板化回调参数的逆变,如 C# 中的逆变
- 如何在没有参数包的情况下编写变差函数
- 通过具有嵌套类的工厂类获取多个变异类模板参数包
- 获取模板参数的成员变量值列表
- 保留短 lambda 用作函数的中间参数,使用 clang 格式保持不变
- 如何定义变体<x,y,z>提取模板参数的子类型
- 正确对齐内存模板,参数顺序不变
- 递归中不同参数类型的变元模板函数
- 通过函数指针传递给变差函数的参数会更改其值
- 提升预定义为带有参数的全局 lambda 的变体访问者
- 使用可变参数模板参数提升变体访问者
- boost ::变体 - 为什么模板参数比const字符串参数具有更高的优先级
- 将变参数包中的值加载到临时数组中
- 使用额外参数提升变体访客
- 正在将动态数组元素解析为参数?(变音符)