MSVC 2017在单个翻译单元中违反了静态初始化顺序。

3

使用-std=c++17的MSVC 2017 Community对以下示例代码支持不佳:

#include <iostream>

struct TC
{
    static TC const values[];
    static TC const& A;
    static TC const& B;
    static TC const& C;

    int const _value;
};

inline constexpr TC const TC::values[]{ { 42 }, { 43 }, { 44 } };
inline constexpr TC const& TC::A{ values[0U] };
inline constexpr TC const& TC::B{ values[1U] };
inline constexpr TC const& TC::C{ values[2U] };

int main(int, char**) noexcept
{
    std::cout << std::boolalpha
        << "&A == &values[0]? " << (&TC::A == &TC::values[0U]) << "\n" 
        << "&B == &values[1]? " << (&TC::B == &TC::values[1U]) << "\n"
        << "&C == &values[2]? " << (&TC::C == &TC::values[2U]) << "\n";
    return 0;
}

预期的输出是:

&A == &values[0]? true
&B == &values[1]? true
&C == &values[2]? true

这是gcc和clang都能生成的内容,但是MSVC会有不同:
&A == &values[0]? true
&B == &values[1]? false
&C == &values[2]? false

如果删除_value成员并且没有用户定义的构造函数,MSVC会给出正确的结果。
由于所有这些都在单个翻译单元中,我的理解是这属于部分有序动态初始化:

2) 部分有序动态初始化适用于所有不是隐式或显式实例化特化的内联变量。如果在每个翻译单元中,部分有序的V在有序或部分有序的W之前被定义,则V的初始化在W的初始化之前(或在程序启动线程时发生)。

我不能使用函数来确保初始化顺序,因为我需要constexprconstexpr对它们的引用。
所以问题是,MSVC在这里违反了标准*,对吗?
*当然cppreference.com不是“标准”,但我假设那里的信息是正确的。

从什么时候开始,将一个对象声明为非constexpr,然后将其定义为constexpr是合法的呢? - ildjarn
好像是要提交反馈给微软,可以在 Visual Studio 的帮助菜单下选择“提交反馈”,或者访问 https://developercommunity.visualstudio.com/spaces/62/index.html(以前是 connect.microsoft.com,但显然已经停用了)。 - SoronelHaetir
@ildjarn 我在标准中没有看到任何禁止这种用法的内容。 - M.M
1
@ildjarn Richard Smith cites the standard - "7.1.5/1 says 'The constexpr specifier shall be applied only to the definition of a variable...'" So constexpr (apparently) is not needed on the declaration. - monkey0506
@monkey_05_06 N3337 是 C++11 的版本。N3797 是介于 C++11 和 C++14(即 N4140)之间的版本。我认为您对该段落的解释存在误解。(如果您想进一步讨论,请另外发布一个问题) - M.M
显示剩余4条评论
1个回答

3

我把同样的问题简化到了这个例子中:

#include <iostream>

int values[3];
constexpr int& v{ values[1] };

int main()
{
    std::cout << &v << ", " << &values[1] << "\n";
    return 0;
}

使用最新的MSVC 2017 Community,输出结果如下:

0119D035, 0119D038

如果删除constexpr,则问题不会出现。 因此,我认为这是一个编译器与constexpr引用初始化相关的bug。该引用被初始化为&values[0]+1个字节,而不是应该是&values[1]
注意:如果有人不熟悉constexpr引用定义,请点击这里或者这里constexpr强制要求初始值具有静态存储期的对象。

谢谢您的确认!我会再等几天看看是否还有其他人有什么要补充的,如果没有的话,我就会将其标记为已接受的答案,并在缺陷报告中引用它。干杯! - monkey0506
毫不意外,MSVC仍然无法处理从诸如&values[0] + 1或者std::addressof(values[0]) + 1这样的表达式进行初始化,但如果我们在指针上使用一些类型转换技巧,它实际上可以正确运行。 reinterpret_cast失败(应该如此),但是MSVC和GCC仍然允许*static_cast<int const*>(static_cast<void const*>(static_cast<char const*>(static_cast<void const*>(&values[0])) + (sizeof(int) * 2))),通过这个方法,MSVC实际上将参考绑定到了正确的对象上(和gcc一样)。clang不允许这种无聊的东西。 - monkey0506
我在你的测试用例上进行了扩展这里(我找不到适用于MSVC C++17的在线编译器)。GCC给出了完全符合预期的输出,但是MSVC 2017 Community显示每个引用都偏移了恰好3*n字节,对于对项IntArray[n]的引用。 - monkey0506
在尝试弄清楚微软开发者社区论坛的代码格式(专业提示:没有)时,经过多次尝试和错误后,我选择了发布屏幕截图。他们的论坛似乎也不允许超链接,但我引用了这个SO问题的编号。 - monkey0506

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接