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

时间:2014-05-22 10:31:51

标签: c++ templates c++11 variadic-templates sfinae

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

不应该使用变量函数模板优先使用省略号函数吗?

有没有解决方法?

2 个答案:

答案 0 :(得分:15)

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

中的哪一个
std::true_type check(); // instantiated from the function template
std::false_type check(...);

是最好的可行候选者,如[over.match.best] 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,可行函数F1被定义为比另一个可行函数F2更好的函数,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
    
         

    - 结束示例]或者,如果没有,

  •   
  • 上下文是通过转换函数初始化,用于直接引用绑定(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.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)更好的转换序列。
  •   

Example

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));

答案 1 :(得分:4)

这对我来说也是令人惊讶的。

一种解决方法可能是将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失败,那么它会尝试第二次过载。

希望有所帮助。