在 gcc 中意外调用了 const 重载。编译器错误或兼容性修复?

Rob*_*b L 8 c++ gcc overloading language-lawyer compiler-bug

我们有一个更大的应用程序,它依赖于 char 和 const char 数组的模板重载。在 gcc 7.5、clang 和 Visual Studio 中,下面的代码在所有情况下都会打印“非常量”。但是,对于 gcc 8.1 及更高版本,输出如下所示:

#include <iostream>

class MyClass
{
public:
    template <size_t N>
    MyClass(const char (&value)[N])
    {
        std::cout << "CONST " << value << '\n';
    }

    template <size_t N>
    MyClass(char (&value)[N])
    {
        std::cout << "NON-CONST " << value << '\n';
    }
};

MyClass test_1()
{
    char buf[30] = "test_1";
    return buf;
}

MyClass test_2()
{
    char buf[30] = "test_2";
    return {buf};
}

void test_3()
{
    char buf[30] = "test_3";
    MyClass x{buf};
}

void test_4()
{
    char buf[30] = "test_4";
    MyClass x(buf);
}

void test_5()
{
    char buf[30] = "test_5";
    MyClass x = buf;
}

int main()
{
    test_1();
    test_2();
    test_3();
    test_4();
    test_5();
}
Run Code Online (Sandbox Code Playgroud)

gcc 8 和 9 输出(来自 Godbolt)是:

CONST test_1
NON-CONST test_2
NON-CONST test_3
NON-CONST test_4
NON-CONST test_5
Run Code Online (Sandbox Code Playgroud)

这在我看来是一个编译器错误,但我想这可能是与语言更改相关的其他问题。有人确切知道吗?

Sto*_*ica 6

当您从一个函数(指定一个函数本地对象)返回一个普通的 id 表达式时,编译器被强制执行两次重载解析。首先,它把它当作一个右值,而不是一个左值。只有在第一次重载决议失败时,才会以对象作为左值再次执行。

[class.copy.elision]

3在以下复制初始化上下文中,可能会使用移动操作而不是复制操作:

  • 如果 return 语句中的表达式是一个(可能带括号的)id 表达式,它命名一个对象,该对象具有在最内部封闭函数或 lambda 表达式的主体或参数声明子句中声明的自动存储持续时间,或

  • ...

为副本选择构造函数的重载决议首先执行,就好像对象是由右值指定的一样。如果第一次重载决议失败或没有执行,或者如果所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是 cv 限定的),则再次执行重载决议,将对象视为左值。[?注:无论是否发生复制省略,都必须执行此两阶段重载决议。如果不执行省略,它确定要调用的构造函数,并且即使调用被省略,所选的构造函数也必须是可访问的。?—?尾注?]

如果我们要添加一个右值重载,

template <size_t N>
MyClass (char (&&value)[N])
{
    std::cout << "RVALUE " << value << '\n';
}
Run Code Online (Sandbox Code Playgroud)

输出将变成

RVALUE test_1
NON-CONST test_2
NON-CONST test_3
NON-CONST test_4
NON-CONST test_5
Run Code Online (Sandbox Code Playgroud)

这是正确的。正如您所见,GCC 的行为是不正确的。它认为第一个重载决议是成功的。那是因为 const 左值引用可能绑定到右值。但是,它会忽略文本“或者如果所选构造函数的第一个参数的类型不是对对象类型的右值引用”。据此,它必须丢弃第一次重载决议的结果,然后再做一次。

好吧,无论如何,这就是 C++17 的情况。当前的标准草案说了一些不同的东西。

如果第一次重载决议失败或未执行,则再次执行重载决议,将表达式或操作数视为左值。

删除了 C++17 之前的文本。所以这是一个时间旅行错误。GCC 实现了 C++20 行为,但即使标准是 C++17,它也会这样做。

  • @TedLyngmo - 对于时间旅行问题,这实际上只是时间问题。我想 Clang 开发人员只是没有抽出时间来实施这一更改。我不会将其称为错误本身。GCC 在 C++17 中执行 new 操作可能是一个错误。取决于此更改如何纳入标准。我不相信有缺陷报告需要对此进行追溯更改,所以我认为这是一个 GCC 错误。支持多种标准是一项细致入微的工作。 (2认同)