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


считаем:

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

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

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

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

5 87

5 ответов:

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

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


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

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

15 Июля '17 P0329R4 был принят в c++20 стандарт:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
Это приносит ограниченную поддержку c99'назначенные инициализаторы С. Это ограничение описывается следующим образом в с. 1.7[diff.Децл].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++, поскольку все или ни один из элементов данных должны быть инициализированы указателями

для c++17 и более ранний импульс на самом деле имеет поддержка назначенных инициализаторов и было много предложений, чтобы добавить поддержку c++ стандартные, например: n4172 и предложение Дэрила Уокера добавить обозначение К Инициализаторам. В предложениях приводится реализация 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.в этом.список]4:

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

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

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

нарушение совместимости с предыдущим c99 реализаций. Здесь требуется явное поведение, определяющее порядок выполнения назначенных инициализаторов.

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

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

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

и использовать его как:

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

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

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

Две Основные Функции C99 что в C++11 отсутствуют упоминания " назначенные инициализаторы и 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    x8,%rsp
   0x00000000004004f4 <+4>: mov    x4005bc,%edi
   0x00000000004004f9 <+9>: xor    %eax,%eax
   0x00000000004004fb <+11>:    callq  0x4003a0 <printf@plt>
   0x0000000000400500 <+16>:    xor    %eax,%eax
   0x0000000000400502 <+18>:    add    x8,%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    x1,%ebx
0x00000000004005d0 <+16>:   mov    x4006a3,%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    x40069c,%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 на самом деле не является постоянным значением времени компиляции.