В +
выражении +[](){}
есть унарный +
оператор. В [expr.unary.op] / 7 это определено следующим образом:
Операнд унарного +
оператора должен иметь арифметический, перечисление без области видимости или тип указателя, а результатом является значение аргумента.
Лямбда не имеет арифметического типа и т. Д., Но может быть преобразована:
[expr.prim.lambda] / 3
Тип лямбда-выражения [...] является уникальным, безымянным типом класса без объединения, называемым типом замыкания , свойства которого описаны ниже.
[expr.prim.lambda] / 6
Тип закрытия для лямбда-выражения , без лямбда-захвата имеет public
не- virtual
не- explicit
const
функцию преобразования в указатель на функцию , имеющую те же значения параметров и возвращаемых типов в качестве оператора вызова функция замыкающим в. Значение, возвращаемое этой функцией преобразования, должно быть адресом функции, которая при вызове имеет тот же эффект, что и вызов оператора вызова функции закрывающего типа.
Следовательно, унарный принудительно +
приводит к преобразованию в тип указателя функции, который предназначен для этой лямбды void (*)()
. Следовательно, тип выражения +[](){}
- это тип указателя на функцию void (*)()
.
Вторая перегрузка void foo(void (*f)())
становится точным совпадением в рейтинге для разрешения перегрузки и поэтому выбирается однозначно (поскольку первая перегрузка НЕ является точным совпадением).
Лямбда [](){}
может быть преобразован в std::function<void()>
через без явного шаблона CTOR из std::function
, который принимает любой тип , который удовлетворяет Callable
и CopyConstructible
требованиям.
Лямбда также может быть преобразована void (*)()
в функцию преобразования типа закрытия (см. Выше).
Оба являются пользовательскими последовательностями преобразования и имеют одинаковый ранг. Вот почему в первом примере разрешение перегрузки не удается из-за неоднозначности.
По словам Кассио Нери, подкрепленного аргументом Даниэля Крюглера, этот унарный +
трюк должен иметь конкретное поведение, то есть на него можно положиться (см. Обсуждение в комментариях).
Тем не менее, я бы рекомендовал использовать явное приведение к типу указателя функции, если вы хотите избежать двусмысленности: вам не нужно спрашивать у SO, что он делает и почему он работает;)
std::bind
кstd::function
объекту, который может вызываться аналогично функции lvalue.operator +()
к типу закрытия без сохранения состояния. Предположим, что этот оператор возвращает нечто иное, чем указатель на функцию, в которую преобразуется тип замыкания. Тогда это изменит наблюдаемое поведение программы, нарушающей 5.1.2 / 3. Пожалуйста, дайте мне знать, согласны ли вы с этим рассуждением.operator +
, но это по сравнению с ситуацией, которой нетoperator +
для начала. Но не указано, что тип замыкания не должен иметьoperator +
перегрузки. «Реализация может определять тип закрытия иначе, чем описано ниже, при условии, что это не изменяет наблюдаемое поведение программы, кроме как на [...]», но добавление оператора IMO не изменяет тип закрытия на что-то отличное от того, что "описано ниже".operator +()
, точно описана стандартом. Стандарт позволяет реализации делать что-то отличное от указанного. Например, добавлениеoperator +()
. Однако, если это различие наблюдается программой, то это незаконно. Однажды я спросил на comp.lang.c ++. Moderated, может ли тип закрытия добавить typedef дляresult_type
и другой,typedefs
необходимый для их адаптации (например, bystd::not1
). Мне сказали, что не может, потому что это можно было наблюдать. Попробую найти ссылку.