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

Рассмотрим:

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

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

Этот код является законным в C99, но не является законным в С++ 11.

Какое обоснование, что С++ 11 не поддерживает такую ​​удобную функцию?

Ответ 1

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

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


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

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

Ответ 2

Boost фактически поддерживает поддержку назначенных Intializers, и было много предложений, чтобы добавить поддержку , например: n4172 и Предложение Daryle Walker о добавлении обозначения в инициализаторы. В предложениях приводится реализация Назначенные инициализаторы в Visual С++, gcc и Clang утверждают:

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

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

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

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

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

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

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

(Visual С++, gcc, и у Клана, похоже, есть согласованное поведение, так как все они будут делать вызовы в этом порядке:) < забастовкa >

  • h()
  • f()
  • g()

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

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

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

So поддержка потребовала бы, чтобы это было выполнено в порядке:

  • f()
  • g()
  • h()

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

введите описание изображения здесь

[источник]

Ответ 4

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

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

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

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

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

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

Ответ 5

Два основных функции C99, что С++ 11 Lacks упоминает "Назначенные инициализаторы и С++".

Я думаю, что "назначенный инициализатор связан с потенциальной оптимизацией. Здесь я использую в качестве примера" 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 <[email protected]>
   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.

Для версии С++

#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 <[email protected]>
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 <[email protected]>
0x00000000004005ec <+44>:   mov    %ebx,%eax
0x00000000004005ee <+46>:   pop    %rbx
0x00000000004005ef <+47>:   retq   

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