在没有参数的情况下调用时,为什么省略号首选为可变参数模板?

eca*_*mur 16 c++ templates sfinae variadic-templates c++11

我正在使用以下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() {
}
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,选择省略号过载时check被调用空的参数列表,因此Predicate<>std::false_type即使SFINAE表达是有效的!

variadic函数模板不应该总是优先于省略号函数吗?

有没有解决方法?

Cas*_*sey 15

T...为空时,编译器执行重载决策以确定哪个

std::true_type check(); // instantiated from the function template
std::false_type check(...);
Run Code Online (Sandbox Code Playgroud)

是[over.match.best] 13.3.3/1(引用N3936)中描述的最佳可行候选者:

定义ICSi(F)如下:

  • 如果F是静态成员函数,则定义ICS1(F)使得ICS1(F)对于任何函数G既不比ICS1(G)更好也不差,并且对称地,ICS1(G)既不好也不比ICS1差(F)132; 除此以外,

  • 让ICSi(F)表示隐式转换序列,它将列表中的第i个参数转换为可行函数F的第i个参数的类型.13.3.3.1定义了隐式转换序列,13.3.3.2定义了它的含义一个隐式转换序列是一个比另一个更好的转换序列或更差的转换序列.

给定这些定义,可行函数F1被定义为比另一个可行函数F2更好的函数,如果对于所有自变量i,ICSi(F1)不是比ICSi(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
    
    Run Code Online (Sandbox Code Playgroud)

    - 结束例子 ]或者,如果不是,

  • 上下文是通过转换函数初始化,用于对函数类型的引用的直接引用绑定(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
    
    Run Code Online (Sandbox Code Playgroud)

    - 结束例子 ]或者,如果不是,

  • F1不是函数模板特化,F2是函数模板特化,或者,如果不是,

  • F1和F2是功能模板特化,根据14.5.6.2中描述的偏序规则,F1的功能模板比F2的模板更专业.

在这种情况下,两个候选项的转换序列都是空的,因为没有参数.倒数第二个子弹是决定因素:

  • F1不是函数模板特化,F2是函数模板特化,或者,如果不是,

因此非模板std::false_type check(...);是优选的.


我的首选解决方法 - 显然有很多 - 将通过省略号转换[over.ics.ellipsis] 13.3.3.1.3/1来制作两个候选模板和区别对象:

当函数调用中的参数与所调用函数的省略号参数规范匹配时,会发生省略号转换序列(参见5.2.2).

通过给"首选"模板声明提供一个明显更好匹配的无关参数,因为任何其他转换序列将优先于每个[over.ics.rank] 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));
Run Code Online (Sandbox Code Playgroud)