Списки инициализаторов и RHS операторов

Я не понимаю, почему списки инициализаторов нельзя использовать в RHS оператора. Рассмотреть возможность:

class foo { };

struct bar
{
    template<typename... T>
    bar(T const&...) { }
};

foo& operator<<(foo& f, bar const&) { return f; }

int main()
{
    foo baz;
    baz << {1, -2, "foo", 4, 5};

    return 0;
}

Последний Clang (также gcc) жалуется:

clang.cc:14:9: error: initializer list cannot be used on the right hand side of operator '<<'
    baz << {1, -2, "foo", 4, 5};
    ^  ~~~~~~~~~~~~~~~~~~~~

    ^  ~~~~~~~~~~~~~~~

Почему стандарт C++ запрещает это? Или, другими словами, почему это терпит неудачу, а не

baz << bar{1, -2, "foo", 4, 5};

?


person mavam    schedule 10.07.2012    source источник
comment
Потому что вы не перегрузили operator<<, чтобы взять initializer_list<> на RHS... Каков ваш фактический вопрос?   -  person ildjarn    schedule 10.07.2012
comment
Я надеялся, что это эквивалентно baz << bar{1, 2, 3, 4, 5};, но, похоже, преобразования не происходит.   -  person mavam    schedule 10.07.2012
comment
Если это поведение, которое вы хотите, возможно, вам следует попробовать дать bar неявный конструктор, который принимает один initializer_list<>.   -  person ildjarn    schedule 10.07.2012
comment
Это не может работать, потому что initializer_list имеет ровно один параметр шаблона, в отличие от конструктора с переменным числом аргументов, имеющего несколько типов аргументов.   -  person mavam    schedule 10.07.2012
comment
Забавно, как это: operator<<(baz, {1, -2, "foo", 4, 5}); работает.   -  person mfontanini    schedule 11.07.2012
comment
@mfontanini: Вы подходите к корню моего вопроса...   -  person mavam    schedule 11.07.2012
comment
Вот обсуждение на эту тему, не стесняйтесь выражать свое мнение groups.google.com/a/isocpp.org/d/msg/std-proposals/nXjimf0amus/   -  person user362515    schedule 19.09.2016


Ответы (1)


Действительно, окончательная версия C++11 не позволяет использовать списки инициализаторов с правой стороны (или с левой стороны, если уж на то пошло) бинарного оператора.

Во-первых, списки-инициализаторы не являются выражениями, как определено в §5 Стандарта. Аргументы функций, а также бинарных операторов, как правило, должны быть выражениями, а грамматика для выражений, определенная в §5, не включает синтаксис для списков инициализации фигурных скобок (т. е. чистых списков инициализаторов; обратите внимание, что имя типа < em>за которым следует скобочный список-инициализации, например, bar {2,5,"hello",7} — это выражение).

Чтобы можно было удобно использовать чистые списки инициализаторов, стандарт определяет различные исключения, которые резюмируются в следующем (ненормативном) примечании:

§8.5.4/1 [...] Примечание. Можно использовать инициализацию списка
— как инициализатор в определении переменной (8.5)
— как инициализатор в новое выражение (5.3.4)
— в операторе возврата (6.6.3)
— как аргумент функции (5.2.2)
— как индекс (5.2.1)< br/> — как аргумент вызова конструктора (8.5, 5.2.3)
— как инициализатор нестатического члена данных (9.2)
— в mem-инициализаторе (12.6.2 )
— в правой части присваивания (5.17)
[...]

Четвертый пункт выше явно разрешает использовать чистые списки инициализаторов в качестве аргументов функций (вот почему operator<<(baz, {1, -2, "foo", 4, 5}); работает), пятый позволяет использовать их в выражениях нижнего индекса (т.е. в качестве аргумента operator[], например, mymap[{2,5,"hello"}] является допустимым), а последний пункт позволяет правая часть присваивания (но не общие бинарные операторы).

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

Что касается причин для этого, черновик/дискуссионный документ N2215, написанный Страуструпом и Дос Рейсом в 2007 году, дает представление о многих проблемах, связанных со списками инициализаторов в различных контекстах. В частности, есть раздел о бинарных операторах (раздел 6.2):

Рассмотрим более общее использование списков инициализаторов. Например:

v = v+{3,4};
v = {6,7}+v;

Когда мы рассматриваем операторы как синтаксический сахар для функций, мы, естественно, считаем приведенное выше эквивалентом

v = operator+(v,{3,4});
v = operator+({6,7},v);

Поэтому естественно распространить использование списков инициализаторов на выражения. Во многих случаях списки инициализаторов в сочетании с операторами являются «естественной» нотацией.
Однако написать грамматику LR(1), допускающую произвольное использование списков инициализаторов, непросто. Блок также начинается с символа {, поэтому использование списка инициализаторов в качестве первого (самого левого) элемента выражения приведет к хаосу в грамматике.
Разрешить списки инициализаторов в качестве правого операнда бинарных операторов тривиально, в нижних индексах и подобных изолированных частях грамматики. Настоящая проблема заключается в том, чтобы разрешить ;a={1,2}+b; в качестве оператора присваивания, не разрешая при этом ;{1,2}+b;. Мы подозреваем, что разрешение списков инициализаторов в качестве правых, но не [sic] в качестве левых аргументов для большинства операторов — это слишком большая [...]

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

Интересно, можно ли упростить проблему, выбрав другой символ вместо фигурных скобок для синтаксиса списка инициализаторов.

person jogojapan    schedule 12.07.2012
comment
Другой символ мог бы сделать больше возможным, но {} является таким естественным расширением инициализаторов массивов и инициализаторов структур POD, унаследованных от C89. - person aschepler; 13.07.2012
comment
Спасибо за это объяснение - я искал тенарный оператор true?{1,2,3}:{4,5,6} - так что это проблема не только бинарных операторов... - person PiotrNycz; 13.06.2014
comment
@PiotrNycz Это может быть не проблема синтаксического анализа, а проблема вывода типа. Обе альтернативы тернарного оператора должны согласовать общий тип, и что-то также должно определять категорию значения результата. Фигурные скобки скрывают смысл программы. В ?: лучше быть явным и помещать имя типа перед {, и это не жертвует выразительной силой. - person Potatoswatter; 12.07.2014
comment
@jogojapan Как вы думаете, оправдано ли это рассуждение - запретить RHS, потому что мы не можем иметь и то, и другое? Разве это не идеальный случай на пути к добру? В конце концов, это мощная функция, по-прежнему полезная только для RHS. Например, для примера OP достаточно RHS, замена аргументов даже не имеет смысла! - person user362515; 19.09.2016
comment
@user362515 user362515 Да, возможно, вы правы. Добавление еще нескольких исключений для RHS (или, возможно, даже включение его для RHS каждого бинарного оператора) могло бы быть лучшим компромиссом. - person jogojapan; 20.09.2016
comment
@jogojapan Да, я не думаю, что только RHS - это кладж - там гораздо чаще требуется дедукция. Я начал обсуждение в надежде, что драконовские правила будут пересмотрены groups.google.com/a/isocpp.org/d/msg/std-proposals/nXjimf0amus/ - person user362515; 20.09.2016