首页 > 解决方案 > 为什么我的 SFINAE 表达式不再适用于 GCC 8.2?

问题描述

我最近将 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) – 在线试用

C++ (clang) – 在线试用

旧版本的 GCC(不幸的是我不记得我之前安装的确切版本)以及 Clang 编译上面的代码就好了,但是 GCC 8.2 给出了一个错误说明:

:在函数'int main()'中:
:29:19: 错误: 重载 'test()' 的调用不明确
     c.test();
                   ^
:12:10: 注意:候选人:'void Class::test() [with U = int&; 类型名 std::enable_if::type>::value>::type ... = {}]'
     无效测试(){
          ^~~~
:22:10: 注意:候选人:'void Class::test() [with U = int&; 类型名 std::enable_if::type>::value)>::type ... = {}]'
     无效测试(){
          ^~~~
:30:25: 错误: 重载 'test()' 的调用不明确
     c.test();
                         ^
:12:10: 注意:候选人:'void Class::test() [with U = const int&; 类型名 std::enable_if::type>::value>::type ... = {}]'
     无效测试(){
          ^~~~
:22:10: 注意:候选人:'void Class::test() [with U = const int&; 类型名 std::enable_if::type>::value)>::type ... = {}]'
     无效测试(){

通常情况下,当不同的编译器和编译器版本以不同的方式处理相同的代码时,我假设我正在调用未定义的行为。标准对上述代码有什么说法?我究竟做错了什么?


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

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

注 3: 已创建 GCC 错误报告

标签: c++c++11gcclanguage-lawyersfinae

解决方案


这是我的看法。总之,clang是对的,gcc有回归。

我们有根据[temp.deduct]p7

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

这意味着无论包是否为空,都必须进行替换。因为我们仍然处于直接上下文中,所以这是 SFINAE-able。

接下来我们知道可变参数确实被认为是实际的模板参数;来自[temp.variadic]p1

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

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

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

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

  • 左值引用类型,

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

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

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


推荐阅读