Можете использовать лямбда-выражение в заголовке файлов нарушает ОУС?


можно ли записать в заголовочный файл следующее:

inline void f () { std::function<void ()> func = [] {}; }

или

class C { std::function<void ()> func = [] {}; C () {} };

Я думаю, что в каждом исходном файле тип лямбды может быть разным, и поэтому содержащийся тип в std::function (target_typeрезультаты будут отличаться).

это ODR (Одно Правило Определения) нарушение, несмотря на то, что выглядит как общая картина и разумная вещь? Нарушает ли второй пример ODR каждый раз или только если хотя бы один конструктор в заголовочном файле?

2 64

2 ответа:

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

однако, это не предназначено. На самом деле, эта проблема уже была затронута некоторое время назад основная проблема 765, который конкретно называет inline функции с внешней связью-такие как f:

7.1.2 [dcl.ПКТ.spec] пункт 4 указывает, что локальные статические переменные и строковые литералы, появляющиеся в теле встроенной функции с помощью внешние связи должны быть одинаковыми в каждой единице перевода в программе. ничего не сказано, однако, о том, являются ли локальные типы также должны быть одинаковы.

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

примечания к июльской встрече 2009 года:

типы должны быть одинаковыми.

теперь резолюция включила следующую формулировку в [dcl.ПКТ.spec] / 4:

тип, определенный в тело Ан extern inline функция имеет один и тот же тип в каждой единице перевода.

(NB: MSVC еще не относится к приведенной выше формулировке, хотя это может быть в следующем выпуске).

поэтому лямбды внутри тел таких функций безопасны, так как определение типа замыкания действительно находится в области блока ([expr.подтянутый.lambda] / 3).
Следовательно, несколько определений f были четко определены.

этот разрешение, конечно же, не охватывает все сценарии, поскольку существует еще много видов сущностей с внешней связью, которые могут использовать лямбды, в частности шаблоны функций - это должно быть охвачено другой основной проблемой.
Тем временем, Itanium уже содержит правила чтобы гарантировать, что такие типы лямбд совпадают в большем количестве ситуаций, следовательно, Clang и GCC должны уже в основном вести себя так, как предполагалось.


Standardese о том, почему отличается типы закрытия-это нарушение ODR. Рассмотрим пункты (6.2) и (6.4) в [basic.защита.odr] / 6:

может быть более одного определения [...]. Учитывая такую сущность с именем D определяется в более чем одной единице перевода, то каждое определение D состоит из та же последовательность жетонов; и

(6.2) - в каждом определением D,соответствующие имена, посмотрел по данным [основная.уважать], должно относиться к субъекту, определенному в рамках определение D, или будет ссылаться на тот же объект, после разрешение перегрузки (за.матч]) и после соответствовать частично специализация шаблона ([temp.за]), [...]; и

(6.4) - в каждом определением D, перегруженные операторы, указанные, элемент неявные вызовы функции преобразования, строителей, оператор новые функции и оператор удаления функций, относятся к та же функция, или к функции, определенной в определении D; [...]

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

в вашем примере с C, тип замыкания определяется внутри класса (область действия которого является наименьшей охватывающей). Если тип замыкания отличается в двух TUs, которые стандарт может непреднамеренно подразумевать с уникальностью типа замыкания, конструктор создает экземпляры и вызывает различные специализации functionшаблон конструктора, нарушающий (6.4) в приведенной выше цитате.

обновлено

ведь я согласен с ответом @ Columbo, но хочу добавить практические пять центов:)

хотя нарушение ODR звучит опасно, это не очень серьезная проблема в данном конкретном случае. Лямбда-классы, созданные в разных TUs, эквивалентны, за исключением их typeids. Поэтому, если вам не нужно справляться с typeid определенной заголовком лямбды (или типом в зависимости от лямбды), вы в безопасности.

теперь, когда ODR нарушение сообщается как ошибка, есть большой шанс, что он будет исправлен в компиляторах, которые имеют проблему, например MSVC и, вероятно, некоторые другие, которые не следуют за Itanium ABI. Обратите внимание, что компиляторы Itanium ABI conformant (например, gcc и clang) уже производят ODR-правильный код для лямбд, определенных заголовком.