尝试删除初始化列表构造函数并不总是有效

Attempting to delete an initializer list constructor does not always take effect

对不起,这个标题很一般,但这是一个令人费解的情况,我无法轻易描述。

假设如下代码:

struct S
{
    S() = default;

    int x;
    int y;
};

S f()
{
    return { 1, 2 };
}

这可以编译并且工作得很好。我想禁止它,因为它容易出错(实际代码要复杂得多)。所以,我尝试添加

template<typename T>
S(std::initializer_list<T>) = delete;

但你猜怎么着 - 没有任何变化。使用 std=c++17 在 Visual Studio 2019 上测试。 C++ resharper 将此显示为错误,但 msvc 实际上编译了它并且它有效。

等等,现在变得有趣了。如果 S() = default; 替换为 S() {},则编译失败并显示

'S::S<int>(std::initializer_list<int>)': attempting to reference a deleted function

好的,这看起来与 user-defined 构造函数和初始化有关?!乱七八糟,但还算可以理解。

但是等等 - 它变得更加有趣 - 保留 = default 构造函数, 但是 使字段 private 也改变了这种行为,你猜怎么着 -该错误与无法访问的成员无关,但它再次显示上面的错误!

所以,为了使这个删除有效,我应该将字段设为私有或定义我自己的空构造函数(忽略未初始化的 x 和 y 字段,这只是一个简化的示例),意思是:

struct S
{
    S() = default;
    // S() {}

    template<typename T>
    S(std::initializer_list<T>) = delete;

private:
    int x;
    int y;
};

clang 13 和 GCC 11 的行为完全相同,而 GCC 9.3 无法编译原始代码(使用 =default 构造函数,public 字段,但删除了初始化列表构造函数)。

知道会发生什么吗?

在 C++17 中,S 被认为是聚合,因此您没有调用任何构造函数,您基本上是直接初始化成员。如果您更改为使用 C++20,S 不再被视为聚合,因为规则已更改并且代码将按预期工作。

更改访问说明符起作用的原因是聚合的所有非静态数据成员的访问说明符需要是 public。让它们成为非 public 意味着您的 class 不再是聚合,并且您不再进行聚合初始化,而是尝试进行列表初始化并且对已删除的构造函数失败。