【问题标题】:Why do my SFINAE expressions no longer work with GCC 8.2?为什么我的 SFINAE 表达式不再适用于 GCC 8.2?
【发布时间】:2019-01-18 03:22:24
【问题描述】:

我最近将 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 给出了一个错误说明:

:在函数'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 bug report created

【问题讨论】:

  • godbolt 开始,它一直工作到 gcc 7.3(你可以在程序集中看到它做了正确的事情)。
  • ::type 之后用... 扩展什么?
  • 删除省略号,将默认的void 替换为int 并添加默认值可以解决问题,是的。真正的问题是,上述代码以前运行良好,但在 GCC 8 中不再运行的原因是什么?
  • @xskxzr 根据这个问题,void... 仅适用于空参数包,这正是这里所需要的。即使它是非法的,在std::enable_if 中用int 替换默认的void 也不会改变问题的任何内容。
  • 最小示例:godbolt.org/g/P9z1pt gcc7.1 OK gcc 8.x KO

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


【解决方案1】:

这是我的看法。总之,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 不符合要求,您的代码(如发布的)格式不正确。

【讨论】:

  • 问题不在于std::enable_ifvoid默认类型!如果我正确理解您的答案,SFINAE 应该适用于不同的类型(例如int)。它不在 GCC 8.2 下。
  • @zennehoy 这就是为什么我在第二句话中说“如果不是void,则回归”:)
  • 你介意减少你的答案吗?我现在在问题中将int 指定为std::enable_if 的类型,因为我真的不打算开始讨论void...,这原本是我简化代码的遗物。请注意,其他人得出的结论与您不同:stackoverflow.com/a/23711944/694509 :)
  • @zennehoy 我不太明白答案;不过谢谢
  • @zenn 其他答案不正确,因为这不是包扩展。它只是一个非扩展的模板参数包,如果你给它一个名字,它可以在其他地方扩展。
【解决方案2】:

我不是语言律师,但下面的引用不能与问题有某种联系吗?

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

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

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

【讨论】:

  • 有趣的是,讨论得出的结论是,这是 Clang 中的一个错误,随后被修复!标准中似乎有相当多的不确定性......
【解决方案3】:

部分答案:将typename = typename enable_if&lt;...&gt;, T=0 与不同的Ts 一起使用:

#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&lt;...&gt;::type... 到底是什么意思知道default type is void

【讨论】:

  • stackoverflow.com/a/23711944/694509,基本要求参数包为空。随意添加 int 来替换默认的 void 类型 - 这不会改变问题的任何内容。
  • @zennehoy 注意:如果没有添加 tparam (int & char),您的两个模板函数具有相同的模板签名,因此会产生歧义。我不明白为什么它以前会起作用。
  • 怎么样?其中一个是template &lt;typename U, void...&gt;,另一个是SFINAE,即template &lt;typename U, [error: std::enable_if has no member type]...&gt;
  • @YSC: typename = std::enable_if_t&lt;cond&gt; 需要“奇怪”的额外虚拟参数,最好使用std::enable_if_t&lt;cond, int&gt; = 0
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-01
  • 2014-01-03
  • 2021-12-01
  • 2016-06-14
  • 2012-07-11
相关资源
最近更新 更多