Почему в C++11 не поддерживает списки инициализации в качестве С99? [закрытый]
считаем:
struct Person
{
int height;
int weight;
int age;
};
int main()
{
Person p { .age = 18 };
}
приведенный выше код является законным в C99, но не законным в C++11.
что это было c++11 обоснование стандартного комитета для исключения поддержки такой удобной функции?
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, похоже, имеют согласованное поведение, поскольку все они будут звонить в этом порядок:)
h()
f()
g()
но неопределенный характер стандарта означает, что если бы эти функции имели какое-либо взаимодействие, то результирующее состояние программы также было бы неопределенным,и компилятор не предупредит вас:есть ли способ получить предупреждение о неправильном поведении назначенных инициализаторов?
c++ тут имеют строгие требования к списку инициализаторов 11.6.4[dcl.в этом.список]4:
в списке инициализаторов связанного списка инициализаторов предложения, включая любые, которые являются результатом расширений пакета (17.5.3), оцениваются в том порядке, в котором они появляются. То есть каждое вычисление значения и побочный эффект, связанный с заданным предложением инициализатора, упорядочивается перед каждым вычислением значения и побочным эффектом, связанным с любым предложением инициализатора это следует за ним в списке инициализатора, разделенном запятыми.
так c++ поддержка потребовала бы, чтобы это было выполнено в следующем порядке:
f()
g()
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
на самом деле не является постоянным значением времени компиляции.