Почему я не могу использовать назначенные инициализаторы со структурами, которые не являются агрегатами?

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};

ошибка: назначенные инициализаторы нельзя использовать с неагрегированным типом «Точка»

Я упускаю что-то очевидное (почему было бы ужасно, если бы назначенные инициализаторы работали со структурами/классами, которые имеют открытые члены, но не являются агрегатами), или это просто недостающая функция, которая просто не добавлена ​​в стандарт?


person NoSenseEtAl    schedule 10.11.2020    source источник
comment
Не забудьте добавить тег c++ ко всем вопросам C++.   -  person cigien    schedule 10.11.2020


Ответы (2)


Агрегатная инициализация (включая инициализацию с разработанными инициализаторами) обходит конструктор класса.

Это не проблема для агрегатов, так как они не могут иметь определяемые пользователем конструкторы. Но если вы разрешите такую ​​инициализацию для классов с пользовательскими конструкторами (которые делают что-то полезное), это может быть вредно.

Рассмотрим этот пример:

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 в деструкторе и не имели бы возможности защититься от такого использования.

person HolyBlackCat    schedule 10.11.2020
comment
поэтому ограничение агрегатами является функцией;) - person 463035818_is_not_a_number; 10.11.2020
comment
ах, это помешало бы конструктору структур полагаться на то, что структура используется только после запуска конструктора... имеет смысл - person NoSenseEtAl; 10.11.2020

Назначенные инициализаторы, где функция взята из 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 )
 {}
};
person Yakk - Adam Nevraumont    schedule 10.11.2020