为什么我的SFINAE表达式不再适用于GCC 8.2?

时间:2018-08-10 13:31:28

标签: c++ c++11 gcc language-lawyer sfinae

我最近将GCC升级到了8.2,并且我的大多数SFINAE表达式都停止了工作。

以下内容略有简化,但演示了该问题:

#include <iostream>
#include <type_traits>

class Class {
public:
    template <
        typename U,
        typename std::enable_if<
            std::is_const<typename std::remove_reference<U>::type>::value, int
        >::type...
    >
    void test() {
        std::cout << "Constant" << std::endl;
    }

    template <
        typename U,
        typename std::enable_if<
            !std::is_const<typename std::remove_reference<U>::type>::value, int
        >::type...
    >
    void test() {
        std::cout << "Mutable" << std::endl;
    }
};

int main() {
    Class c;
    c.test<int &>();
    c.test<int const &>();
    return 0;
}

C++ (gcc) – Try It Online

C++ (clang) – Try It Online

GCC的旧版本(不幸的是,我不记得之前安装的确切版本)以及Clang可以很好地编译上面的代码,但是GCC 8.2给出了错误提示:

 : In function 'int main()':
:29:19: error: call of overloaded 'test()' is ambiguous
     c.test();
                   ^
:12:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value>::type ... = {}]'
     void test() {
          ^~~~
:22:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value)>::type ... = {}]'
     void test() {
          ^~~~
:30:25: error: call of overloaded 'test()' is ambiguous
     c.test();
                         ^
:12:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value>::type ... = {}]'
     void test() {
          ^~~~
:22:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value)>::type ... = {}]'
     void test() {

通常,当不同的编译器和编译器版本以不同的方式处理同一代码时,我假定我正在调用未定义的行为。该标准对上述代码有什么要求?我在做什么错了?


注意:问题不是解决此问题的方法,我想到了几种方法。问题是为什么在GCC 8中不起作用-是标准未定义,还是编译器错误?

注释2:由于每个人都在使用默认的void类型std::enable_if,因此我将问题改为使用int。问题仍然存在。

注释3: GCC bug report created

3 个答案:

答案 0 :(得分:5)

这是我的看法。简而言之,clang是正确的,而gcc具有回归。

我们根据[temp.deduct]p7

  

替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。 [...]

这意味着无论包装是否为空,都必须进行替换。因为我们仍处于即时环境中,所以可以使用SFINAE。

接下来,我们将可变参数确实视为实际的模板参数;来自[temp.variadic]p1

  

模板参数包是一个接受零个或多个模板参数的模板参数。

[temp.param]p2表示允许使用哪些非类型模板参数:

  

非类型模板参数应具有以下类型之一(可选的cv限定):

     
      
  • 一种文字类型,具有强等式结构([class.compare.default]),没有可变或易变的子对象,并且如果存在默认成员运算符<=>,则为宣布公开

  •   
  • 左值引用类型

  •   
  • 包含占位符类型([dcl.spec.auto])的类型,或

  •   
  • 推导的类类型([dcl.type.class.deduct])的占位符。

  •   

请注意,void不符合要求,您的代码(发布时)格式不正确。

答案 1 :(得分:1)

我不是语言律师,但以下引用不能以某种方式与问题相关吗?

  

[temp.deduct.type/9]:如果Pi是一个包扩展,则将Pi的模式与A的模板参数列表中的每个剩余参数进行比较。每个比较都推导模板参数包中后续位置扩展了的模板参数,由皮

在我看来,由于模板参数列表中没有剩余参数,因此没有模式(包含enable_if)的比较。如果没有比较,那么也就没有扣除,我相信扣除之后会发生替代。因此,如果没有替代项,则不会应用SFINAE。

如果我错了,请纠正我。我不确定该特定段落是否适用于此,但是关于[temp.deduct]中的包扩展,还有更多类似的规则。此外,此讨论还可以帮助更有经验的人解决整个问题:https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A

答案 2 :(得分:0)

部分答案:将print与不同的typename = typename enable_if<...>, T=0一起使用:

T

demo

仍然试图弄清楚#include <iostream> #include <type_traits> class Class { public: template < typename U, typename = typename std::enable_if_t< std::is_const<typename std::remove_reference<U>::type>::value >, int = 0 > void test() { std::cout << "Constant" << std::endl; } template < typename U, typename = typename std::enable_if_t< !std::is_const<typename std::remove_reference<U>::type>::value >, char = 0 > void test() { std::cout << "Mutable" << std::endl; } }; int main() { Class c; c.test<int &>(); c.test<int const &>(); return 0; } 到底意味着什么,知道default type is void