Зависимое выражение и использование ODR в Generic Lambda в C++14

   void f(      int , const int (&)[2] = {}) { } // #1 
   void f(      int , const int (&)[1]     ) { } // #2 
// void f(const int&, const int (&)[1]     ) { } // #2_original 

void test() { 

    const int x = 17; 

    auto g = [](auto a) { 
        f(x); // OK: calls #1, does not capture x 
    }; 

    auto g2 = [ /* = */ ](auto a) { 
        int selector[ sizeof(a) == 1 ? 1 : 2 ]{}; 
        f(x, selector); // OK: is a dependent expression, so captures x ??? 
    }; 
} 

Это пример из стандарта С++ 14 (ISO/IEC 14882:2014), раздел 5.1.2, параграф 12, который я изменил двумя способами:

  • Во-первых, обе версии функции f() имеют int в качестве первого аргумента, поэтому переменная x в любом случае не odr-используется.
  • Во-вторых, я удалил (закомментировал) capture-default в лямбда-выражении g2.

Соответствует ли этот код стандарту? И clang, и gcc успешно компилируются. Однако в исходном примере лямбда g2 имела capture-default ([=]), поэтому переменная x была неявно захвачена, поскольку было зависимое выражение (а также потому, что она могла быть odr-used< /em> в функции f() #2_original). Обратите внимание, что в вышеупомянутом параграфе Стандарта есть 2 условия для неявного захвата переменной x (odr-use зависимое выражение). Теперь без capture-default и odr-use:

  • Не должно ли это быть ошибкой времени компиляции, поскольку есть зависимое выражение и нет capture-default? То есть переменная x должна быть захвачена, но это невозможно (предположим, что g2 вызывается с обоими типами аргументов, т. е. одни возвращают sizeof(a)=1, а другие sizeof(a)>1).

  • Или условие зависимого выражения для неявного захвата переменной применяется только при наличии capture-default? Это означает, что без odr-use (т. е. без const int& в функции f() #2) программа будет работать одинаково независимо от capture-default. Следовательно, не будет ли бесполезным второе условие относительно зависимого выражения?

Это стандарт С++ 14 (ISO/IEC 14882:2014), раздел 5.1.2, пункт 12 (выделено мной):

лямбда-выражение со связанным capture-default, которое явно не фиксирует this или переменную с автоматическим сохранением длительность (это исключает любое выражение-id-выражение, которое, как было обнаружено, ссылается на нестатический элемент данных, связанный с init-capture), считается неявно захватить объект (т. е. это или переменную), если составной оператор:

  • odr-использует (3.2) сущность, или
  • называет сущность в потенциально вычисляемом выражении (3.2), где объемлющее полное выражение зависит от универсального лямбда-параметра, объявленного в пределах досягаемости лямбда-выражения.

Примечание: лямбда g не захватывает переменную x, потому что она не odr-используется в f(x) (см. Стандарт C++14 (ISO/IEC 14882:2014), раздел 5.1.2, параграф 13).

Ссылки:


person José Luis    schedule 03.03.2017    source источник


Ответы (1)


Соответствует ли этот код стандарту?

да. Правило:

Если лямбда-выражение или экземпляр шаблона оператора вызова функции общего лямбда-odr-использует (3.2) this или переменную с автоматическим сроком хранения из области достижения, этот объект должен быть захвачен лямбда-выражение.

Использует ли лямбда-одр x? Нет, из-за [basic.def.odr]:

Переменная x, имя которой появляется как потенциально вычисляемое выражение ex, odr-используется ex, если только применение преобразования lvalue-to-rvalue (4.1) к x не дает константное выражение (5.20), которое не вызывать любые нетривиальные функции и, если x является объектом, ex является элементом множества потенциальных результатов выражения e, где либо преобразование lvalue-to-rvalue (4.1) применяется к e, либо e является выражение отброшенного значения (раздел 5).

x — это целочисленная константа, которая используется в выражении, где применяется преобразование lvalue-to-rvalue, поэтому она не используется odr. Поскольку он не используется odr, это не ошибка, что вы его не фиксируете.

Это было бы неправильно, если бы существовала перегрузка f, которая брала свой первый аргумент по ссылке - этот экземпляр оператора вызова будет использовать odr-использовать x, но он не захвачен, что делает его неправильно сформированным. .


Раздел, который вы цитируете, не имеет отношения к вашему модифицированному примеру. Это относится только к "лямбда-выражению со связанным capture-default". Но у вашей лямбды нет capture-default. capture-default — это либо =, либо &, у вводящего [] нет Capture-default. Однако, если бы у нас было [=] или [&], этот раздел объяснял бы, почему будет захвачено x.

person Barry    schedule 04.03.2017
comment
Другими словами, случай capture-default фиксирует его, чтобы быть безопасным, потому что вы не можете на самом деле сказать, будет ли зависимое выражение использоваться odr или нет, до момента создания экземпляра, и это просто глупо, если [=] или [&] дает вам отсутствующую ошибку захвата. - person T.C.; 05.03.2017
comment
@Barry Я так и думал, но если это правильно, мой пример будет компилироваться и вести себя точно так же, с захватом переменной x или без него (с или без capture-default): 1) Без capture -default переменная x не захватывается, потому что она не odr-используется 2) С capture-default переменная x захватывается из-за зависимого выражения (даже если он не odr-используется ни в одной из двух версий функции f()!!). Таким образом, захват переменной x зависит (в данном случае) от того, помещает ли программист capture-default или нет, а не от того, нужен ли он лямбде (odr-uses и т. д. .) - person José Luis; 06.03.2017
comment
@Т.С. Означает ли это, что переменная x должна быть захвачена только из-за зависимого выражения, даже если невозможно odr-use? Кстати, мне нравится ваш подход к «упреждающему» захвату. Я думаю, что так я понимаю это лучше. Если существует зависимое выражение, которое может сделать переменную x используемой по умолчанию, переменная x должна быть захвачена на всякий случай, независимо от того, существует ли захват по умолчанию или нет. Это правильно? Однако я не уверен, что формулировка второго абзаца пункта 5.1.2 пункта 12 Стандартов помогает этому соответствовать (правда, для меня жесткая формулировка). - person José Luis; 06.03.2017
comment
Я обновил свой пост таблицей и вопросом, пытаясь обобщить все (возможно, мое непонимание происходит из-за того, что я точно не знаю, что компилятор может знать о зависимом выражении) - person José Luis; 06.03.2017
comment
@JoséLuis Я отменил ваше редактирование - один вопрос на вопрос. Кроме того, таблица была неправильной, при захвате по умолчанию x будет безоговорочно захвачено, поскольку оно используется в зависимом выражении. - person Barry; 06.03.2017
comment
@Barry Хорошо, я думал, что это еще один пункт того же основного вопроса, но я опубликую новый отдельный вопрос. Что касается таблицы, мне показалось очень полезным показать все возможные случаи, и я думаю, что это поможет другим людям, которые столкнутся с той же проблемой в будущем. Можете ли вы подтвердить, что это нормально, если я отредактирую его, изменив регистр Capture-Default/f(int, ...)#2 с «Не захватывает x» на «Захват x»? Спасибо за вашу помощь. - person José Luis; 08.03.2017
comment
@JoséLuis Capture определяется во время написания лямбда-выражения, но компилятор не может определить, включает ли зависимое выражение использование odr или нет, пока оно не будет фактически создано. Таким образом, с параметром «capture-default» (который, по сути, говорит: «Привет, компилятор, захватите все, что нужно этой лямбде»), лямбда захватит все, что может понадобиться. OTOH, программист знает о коде больше, чем компилятор, поэтому, если вы знаете, что не будет использования odr, вы можете опустить захват, и компилятор не будет стоять у вас на пути, пока вы на самом деле не попробуете odr-используйте его. - person T.C.; 08.03.2017