Устранение неоднозначной перегрузки по указателю на функцию и std:: function для лямбды с использованием +

В следующем коде первый вызов foo неоднозначен и поэтому не компилируется.

Второй, с добавленным + до лямбда, разрешает перегрузку указателя функции.

#include <functional>

void foo(std::function<void()> f) { f(); }
void foo(void (*f)()) { f(); }

int main ()
{
    foo(  [](){} ); // ambiguous
    foo( +[](){} ); // not ambiguous (calls the function pointer overload)
}

Что такое обозначение + здесь?

Ответ 1

+ в выражении +[](){} является унарным оператором +. Он определяется следующим образом в [Expr.unary.op]/7:

Операнд унарного оператора + должен иметь арифметическое, неперечисленное перечисление или тип указателя, а результат - значение аргумента.

Лямбда не имеет арифметического типа и т.д., но может быть преобразована:

[expr.prim.lambda]/3

Тип лямбда-выражения [...] - это уникальный, неназванный тип неединичного класса, называемый типом замыкания, свойства которого описаны ниже.

[expr.prim.lambda]/6

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

Следовательно, унарный + принудительно преобразует в тип указателя функции, который для этого lambda void (*)(). Таким образом, тип выражения +[](){} - это тип указателя функции void (*)().

Вторая перегрузка void foo(void (*f)()) становится Точным соответствием в рейтинге для разрешения перегрузки и поэтому выбрана однозначно (поскольку первая перегрузка НЕ ​​является точной совпадением).


lambda [](){} можно преобразовать в std::function<void()> через неявный шаблон ctor std::function, который принимает любой тип, удовлетворяющий требованиям Callable и CopyConstructible.

Лямбда также может быть преобразована в void (*)() через функцию преобразования типа замыкания (см. выше).

Оба являются пользовательскими последовательностями преобразования и того же ранга. Вот почему в первом примере ошибка перегрузки возникает из-за двусмысленности.


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

Тем не менее, я бы рекомендовал использовать явный приведение к типу указателя функции, если вы хотите избежать двусмысленности: вам не нужно спрашивать о том, что делает и почему оно работает;)