为什么我不能对非聚合的结构使用指定的初始化器?

Why I can not use designated initalizers with structs that are not aggregates?

C++ 有一个不错的新特性:

struct Point{
int x;
int y;
int z; 
};

Point p{.x=47, .y=1701, .z=0};

但是如果我添加一个构造函数,那么我将被禁止使用漂亮的指定初始化语法:

struct Point{
Point(int x, int y, int z = 0): x(x), y(y), z(z){}
int x;
int y;
int z; 
};

static Point p{.x=47, .y=1701, .z = 0};

error: designated initializers cannot be used with a non-aggregate type 'Point'

我是否遗漏了一些明显的东西(为什么如果指定的初始化程序与具有 public 成员但不是聚合的 structs/classes 一起工作会很糟糕)或者这只是一个缺失的功能没有加入标准?

聚合初始化(包括使用设计的初始化程序进行初始化)绕过 class.

的构造函数

这对聚合来说不是问题,因为它们不允许有用户定义的构造函数。但是,如果您允许使用用户提供的构造函数(做一些有用的事情)对 classes 进行这种初始化,它可能是有害的。

考虑这个例子:

class A
{
    static std::map<A *, int> &Indices()
    {
        static std::map<A *, int> ret;
        return ret;
    }

  public:
    int dummy = 0;

    A(int index)
    {
        Indices().emplace(this, index);
    }

    A(const A &) = delete;
    A &operator=(const A &) = delete;
    
    ~A()
    {
        auto it = Indices().find(this);
        std::cout << "Deleting #" << it->second << '\n';
        Indices().erase(it);
    }
};

如果您能够做到 A{.dummy = 42};,您将在析构函数中获得 UB,并且无法防止这种用法。

从 C 中提取的功能的指定初始化器。大多数 C++ 编译器也是 C 编译器,它首先是 C 功能。

他们添加了一个限制(初始化器是有序的)并将其应用于匹配 C 类型的 C++ 类型,并将其引入 C++。大多数主要的 C++ 编译器已经将它作为 C++ 扩展(没有限制);与编译器实现者一起检查限制是否合理,然后添加该功能的“成本”非常低。

一旦有了构造函数,它就变成了一个更大的语言问题。初始化程序是否引用构造函数参数?如果是,我们 运行 进入参数名称不唯一的问题。如果不是,那么当构造函数设置一个值而初始化器设置一个不同的值时我们如何处理?

基本上我们需要按名称的函数参数来获得带有构造函数的合理的指定初始值设定项。这是一项新功能,而不是简单地从 C 中提取的功能。

解决方法(针对命名参数)是:

struct RawPoint{
  int x = 0;
  int y = 0;
  int z = 0;
};

struct Point {
 Point( int x_, int y_, int z_ = 0 ):
   x(x_), y(y_), z(z_)
 {}
 explicit Point( RawPoint pt ):
   Point( pt.x, pt.y, pt.z )
 {}
  int x, y, z;
};

那么你可以这样做:

Point pt( {.x=3} );

通过访问 RawPoint 的指定初始化程序功能。

这与您在函数调用中指定初始值设定项的方式相同。

这也有效:

struct Point:RawPoint {
 Point( int x, int y, int z = 0 ):
   RawPoint{x,y,z}
 {}
 explicit Point( RawPoint pt ):
   RawPoint( pt )
 {}
};