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

Why do my SFINAE expressions no longer work with 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) – 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: 由于每个人都跳转到 std::enable_if 的默认 void 类型,我已将问题更改为使用 int 反而。问题依旧。

注3: GCC bug report created

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

#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;
}

(demo)

仍在尝试弄清楚 std::enable_if<...>::type... 到底是什么意思,知道 default type is void

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

[temp.deduct.type/9]: If Pi is a pack expansion, then the pattern of Pi is compared with each remaining argument in the template argument list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by Pi.

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

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

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

我们根据 [temp.deduct]p7:

The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. [...]

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

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

A template parameter pack is a template parameter that accepts zero or more template arguments.

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

A non-type template-parameter shall have one of the following (optionally cv-qualified) types:

  • a type that is literal, has strong structural equality ([class.compare.default]), has no mutable or volatile subobjects, and in which if there is a defaulted member operator<=>, then it is declared public,

  • an lvalue reference type,

  • a type that contains a placeholder type ([dcl.spec.auto]), or

  • a placeholder for a deduced class type ([dcl.type.class.deduct]).

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