为什么在没有参数的情况下调用省略号比可变参数模板更可取?

Why is an ellipsis preferred to a variadic template when called with no arguments?

本文关键字:参数 变参 省略号 调用 情况下 为什么      更新时间:2023-10-16

我使用以下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

希望对你有帮助。