Почему C ++ 11 не поддерживает назначенные списки инициализаторов как C99?

Рассматривать:

struct Person
{
int height;
int weight;
int age;
};

int main()
{
Person p { .age = 18 };
}

Приведенный выше код допустим в C99, но не разрешен в C ++ 11.

Что это было Обоснование стандартного комитета для исключения поддержки такой удобной функции?

90

Решение

C ++ имеет конструкторы. Если имеет смысл инициализировать только один элемент, то это можно выразить в программе, реализовав соответствующий конструктор. Это своего рода абстракция, которую продвигает C ++.

С другой стороны, назначенная функция инициализаторов больше связана с раскрытием и упрощением доступа к членам непосредственно в коде клиента. Это приводит к таким вещам, как наличие человека 18 лет (лет?), Но с ростом и весом ноль.


Другими словами, назначенные инициализаторы поддерживают стиль программирования, в котором раскрываются внутренние компоненты, и клиенту предоставляется гибкость в выборе способа использования типа.

C ++ больше заинтересован в том, чтобы придать гибкость стороне дизайнер вместо этого типа, поэтому дизайнеры могут упростить использование типа и затруднить его использование. Контроль дизайнера за инициализацией типа является частью этого: дизайнер определяет конструкторы, инициализаторы в классе и т. Д.

29

Другие решения

15 июля 17 года P0329R4 был принят в стандарт: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
Это приносит ограниченную поддержку Назначенные инициализаторы. Это ограничение описывается следующим образом в C.1.7 [diff.decl] .4, учитывая:

struct A { int x, y; };
struct B { struct A a; };

Следующие назначенные инициализации, действительные в C, ограничены в C ++:

  • struct A a = { .y = 1, .x = 2 } недопустимо в C ++, потому что указатели должны появляться в порядке объявления членов данных
  • int arr[3] = { [1] = 5 } недопустим в C ++, потому что инициализация массива не поддерживается
  • struct B b = {.a.x = 0} недопустимо в C ++, потому что указатели не могут быть вложенными
  • struct A c = {.x = 1, 2} недопустим в C ++, потому что либо все, либо ни один из членов данных не должен быть инициализирован указателями

За и раньше Boost на самом деле имеет поддержка назначенных инициализаторов и были многочисленные предложения добавить поддержку стандарт, например: n4172 а также Предложение Дэрил Уокер добавить обозначение в инициализаторы. Предложения ссылаются на реализацию Назначенные инициализаторы в Visual C ++, gcc и Clang утверждают:

Мы считаем, что изменения будут относительно простыми для реализации

Но стандартный комитет неоднократно отклоняет такие предложения, заявив:

EWG обнаружила различные проблемы с предложенным подходом и не думала, что можно попытаться решить проблему, так как она была опробована много раз и каждый раз, когда она терпела неудачу

Комментарии Бена Фойгта помогли мне увидеть непреодолимые проблемы с этим подходом; дано:

struct X {
int c;
char a;
float b;
};

В каком порядке эти функции будут вызываться в : struct X foo = {.a = (char)f(), .b = g(), .c = h()}? Удивительно, но в :

Порядок вычисления подвыражений в любом инициализаторе является неопределенным [1]

(Visual C ++, НКУ, и Clang, похоже, ведут себя согласованно, поскольку все они будут делать звонки в этом порядке 🙂

  1. h()
  2. f()
  3. g()

Но неопределенный характер стандарта означает, что если бы эти функции имели какое-либо взаимодействие, результирующее состояние программы также было бы неопределенным, и компилятор не предупредит вас: Есть ли способ получить предупреждение о неправильном поведении назначенных инициализаторов?

делает иметь строгие требования к списку инициализаторов 11.6.4 [dcl.init.list] 4:

В пределах списка инициализатора списка фигурных скобок предложения инициализатора, включая любые, которые являются результатом расширений пакета (17.5.3), оцениваются в порядке, в котором они появляются. Таким образом, каждое вычисление значения и побочный эффект, связанный с данным предложением инициализатора, секвенируются перед каждым вычислением значения и побочным эффектом, связанным с любым предложением инициализатора, которое следует за ним в списке через запятую списка инициализатора.

Так Служба поддержки потребовала бы, чтобы это было выполнено в следующем порядке:

  1. f()
  2. g()
  3. h()

Нарушение совместимости с предыдущими Реализации.
Как обсуждалось выше, эта проблема была обойдена ограничениями на Назначенные инициализаторы, принятые в . Они обеспечивают стандартизированное поведение, гарантирующее порядок выполнения назначенных инициализаторов.

58

Назначенные инициализаторы в настоящее время включены в C ++ 20 основной работы: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf так что мы могли бы наконец увидеть их!

22

Немного хакерства, так что просто делюсь ради удовольствия.

#define with(T, ...)\
([&]{ T ${}; __VA_ARGS__; return $; }())

И используйте это как:

MyFunction(with(Params,
$.Name = "Foo Bar",
$.Age  = 18
));

который расширяется до:

MyFunction(([&] {
Params ${};
$.Name = "Foo Bar", $.Age = 18;
return $;
}()));
9

Две основные особенности C99 что в C ++ 11 Lacks упоминается «Назначенные инициализаторы и C ++».

Я думаю, что «назначенный инициализатор» связан с потенциальной оптимизацией. Здесь я использую «gcc / g ++» 5.1 в качестве примера.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
int x;
int y;
};
const struct point a_point = {.x = 0, .y = 0};
int foo() {
if(a_point.x == 0){
printf("x == 0");
return 0;
}else{
printf("x == 1");
return 1;
}
}
int main(int argc, char *argv[])
{
return foo();
}

Мы знали во время компиляции, a_point.x равен нулю, поэтому мы могли ожидать, что foo оптимизирован в единый printf,

$ gcc -O3 a.c
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function foo:
0x00000000004004f0 <+0>: sub    $0x8,%rsp
0x00000000004004f4 <+4>: mov    $0x4005bc,%edi
0x00000000004004f9 <+9>: xor    %eax,%eax
0x00000000004004fb <+11>:    callq  0x4003a0 <printf@plt>
0x0000000000400500 <+16>:    xor    %eax,%eax
0x0000000000400502 <+18>:    add    $0x8,%rsp
0x0000000000400506 <+22>:    retq
End of assembler dump.
(gdb) x /s 0x4005bc
0x4005bc:   "x == 0"

foo оптимизирован для печати x == 0 только.

Для версии C ++,

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
point(int _x,int _y):x(_x),y(_y){}
int x;
int y;
};
const struct point a_point(0,0);
int foo() {
if(a_point.x == 0){
printf("x == 0");
return 0;
}else{
printf("x == 1");
return 1;
}
}
int main(int argc, char *argv[])
{
return foo();
}

И это вывод оптимизированного кода сборки.

g++ -O3 a.cc
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function _Z3foov:
0x00000000004005c0 <+0>:    push   %rbx
0x00000000004005c1 <+1>:    mov    0x200489(%rip),%ebx        # 0x600a50 <_ZL7a_point>
0x00000000004005c7 <+7>:    test   %ebx,%ebx
0x00000000004005c9 <+9>:    je     0x4005e0 <_Z3foov+32>
0x00000000004005cb <+11>:   mov    $0x1,%ebx
0x00000000004005d0 <+16>:   mov    $0x4006a3,%edi
0x00000000004005d5 <+21>:   xor    %eax,%eax
0x00000000004005d7 <+23>:   callq  0x400460 <printf@plt>
0x00000000004005dc <+28>:   mov    %ebx,%eax
0x00000000004005de <+30>:   pop    %rbx
0x00000000004005df <+31>:   retq
0x00000000004005e0 <+32>:   mov    $0x40069c,%edi
0x00000000004005e5 <+37>:   xor    %eax,%eax
0x00000000004005e7 <+39>:   callq  0x400460 <printf@plt>
0x00000000004005ec <+44>:   mov    %ebx,%eax
0x00000000004005ee <+46>:   pop    %rbx
0x00000000004005ef <+47>:   retq

Мы это видим a_point на самом деле не является постоянной величиной времени компиляции.

5
По вопросам рекламы [email protected]