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


Answers

У Boost фактически есть поддержка назначенных Intializers, и было предложено множество предложений по поддержке поддержки стандарта c ++ , например: n4172 и предложение Daryle Walker о добавлении обозначения в инициализаторы . В предложениях приводится реализация назначенных инициализаторов c99 в Visual C ++, gcc и Clang:

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

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

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

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

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

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

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

(Visual C ++, gcc и Clang, похоже, имеют согласованное поведение, поскольку все они будут делать вызовы в этом порядке :)

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

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

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

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

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

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

Нарушение совместимости с предыдущими реализациями c99 . Здесь требуется явное поведение, определяющее порядок выполнения назначенных Intializers. Более многообещающим предложением для Основной рабочей группы является P0329R3, в котором предлагается назначенная инициализация со следующими ограничениями:

[ источник ]

Question

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

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

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

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

В чем причина того, что C ++ 11 не поддерживает такую ​​удобную функцию?




Два основных элемента 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 не является значением постоянной времени компиляции.