Вирішення неоднозначного перевантаження покажчика на функцію та функції std :: для лямбда за допомогою +


93

У наступному коді перший виклик до 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)
}

Що +тут робить позначення?

Відповіді:


98

У +виразі +[](){}є унарний +оператор. Це визначається таким чином у [expr.unary.op] / 7:

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

Лямбда не має арифметичного типу тощо, але її можна перетворити:

[вираз.prim.lambda] / 3

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

[вираз.prim.lambda] / 6

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

Отже, унарний +примушує перетворити на тип покажчика на функцію, який є для цієї лямбда void (*)(). Отже, тип виразу +[](){}- це тип покажчика на функцію void (*)().

Друге перевантаження void foo(void (*f)())стає Точним збігом у рейтингу щодо дозволу перевантаження і тому вибирається однозначно (оскільки перше перевантаження НЕ є Точним збігом).


Лямбда [](){}може бути перетворена за std::function<void()>допомогою неявного шаблону ctor of std::function, який приймає будь-який тип, що відповідає вимогам Callableand CopyConstructible.

На лямбду також можна перетворити за void (*)()допомогою функції перетворення типу закриття (див. Вище).

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


За словами Кассіо Нері, підкріпленого аргументом Даніеля Крюглера, цим одинарним +трюком слід визначити поведінку, тобто на нього можна покластися (див. Обговорення в коментарях).

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


3
Вказівники на функції @Fred AFAIK не можуть бути перетворені на вказівники на функції, що не входять, не кажучи вже про функції lvalues. Ви можете зв'язати функцію - член через std::bindдо std::functionоб'єкту , який може бути названий аналогічно функції Lvalue.
dyp

2
@DyP: Я вважаю, що ми можемо покластися на складне. Дійсно, припустимо, що реалізація додає operator +()типу закриття без стану. Припустимо, що цей оператор повертає щось інше, ніж покажчик, для функції, в яку перетворюється тип закриття. Потім це змінить спостережливу поведінку програми, яка порушує 5.1.2 / 3. Будь ласка, дайте мені знати, якщо ви погоджуєтесь із цими аргументаціями.
Кассіо Neri

2
@CassioNeri Так, це той момент, коли я не впевнений. Я згоден, що спостерігається поведінка може змінитися при додаванні operator +, але це порівняно із ситуацією, з якої не operator +можна починати. Але не зазначено, що тип закриття не повинен мати operator +перевантаження. "Реалізація може визначати тип закриття інакше, ніж описано нижче, за умови, що це не змінює спостережувану поведінку програми, крім [...]", але додавання оператора IMO не змінює тип закриття на щось інше, ніж те, що "описано нижче".
dyp

3
@DyP: Ситуація, коли немає, - operator +()це саме та, яка описана стандартом. Стандарт дозволяє реалізації робити щось інше, ніж зазначене. Наприклад, додавання operator +(). Однак, якщо ця різниця спостерігається програмою, то це незаконно. Одного разу я запитав у comp.lang.c ++. Модерував, чи може тип закриття додати typedef для result_typeта інший, typedefsнеобхідний для їх адаптації (наприклад, by std::not1). Мені сказали, що не може, бо це можна спостерігати. Я спробую знайти посилання.
Cassio Neri

6
VS15 видає цю веселу помилку: test.cpp (543): помилка C2593: 'operator +' є неоднозначним t \ test.cpp (543): примітка: може бути 'вбудований оператор C ++ + (void (__cdecl *) (void )) 't \ test.cpp (543): примітка: або' вбудований оператор C ++ + (void (__stdcall *) (void)) 't \ test.cpp (543): примітка: або' вбудований оператор C ++ + (void (__fastcall *) (void)) 't \ test.cpp (543): примітка: або' вбудований оператор C ++ + (void (__vectorcall *) (void)) 't \ test.cpp (543): примітка : під час спроби зіставити список аргументів '(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>) test.cpp (543): помилка C2088:' + ': заборонено для класу
Ед Ламберт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.