Подтвердить что ты не робот

Почему оператор() изменяется для std:: function в С++ 17?

Следующий код предположительно является незаконным в С++ 14, но является законным в С++ 17:

#include <functional>

int main()
{
    int x = 1729;
    std::function<void (int&)> f(
        [](int& r) { return ++r; });
    f(x);
}

Не утруждайте себя тестированием, вы получите противоречивые результаты, затрудняющие вопрос о том, является ли это ошибкой или преднамеренным поведением. Однако, сравнивая два черновика (N4140 против N4527, их можно найти на github.com/cplusplus/draft), есть одна существенная разница в [func.wrap.func.inv]. Пункт 2:

Возвращает: ничего, если R является void, иначе возвращаемое значение INVOKE (f, std:: forward (args)..., R).

Вышеуказанное было удалено между черновиками. Импликация заключается в том, что возвращаемое значение лямбда теперь тихо отбрасывается. Это кажется неправильной. Может ли кто-нибудь объяснить рассуждения?

4b9b3361

Ответ 1

В <стандартном был нелепый дефект о std::function<void(Args...)>. По формулировке стандарта нет (нетривиальное) 1 использование std::function<void(Args...)> было законным, потому что ничто не может быть "неявно преобразовано в" void (даже не void).

void foo() {} std::function<void()> f = foo; не был законным в С++ 14. К сожалению.

Некоторые компиляторы взяли плохую формулировку, которая сделала std::function<void(Args...)> совершенно бесполезной и применила логику только к переданным вызовам, где возвращаемое значение не было void. Затем они пришли к выводу, что было запрещено передавать функцию, возвращающую int в std::function<void(Args...)> (или любой другой тип не void). Они не довели до логического конца и запретили функции, возвращающие void (требования std::function не делают особого случая для точно совпадающих подписей: применяется одна и та же логика).

Другие компиляторы просто проигнорировали плохую формулировку в случае типа возврата void.

В основном дефект заключался в том, что возвращаемый тип выражения вызова должен быть неявно конвертируемым - к типу возврата подписи std::function (более подробную информацию см. в ссылке выше). И по стандарту void нельзя неявно преобразовать в void 2.

Таким образом, дефект был разрешен. std::function<void(Args...)> теперь принимает все, что может быть вызвано с помощью Args..., и отбрасывает возвращаемое значение, как и многие существующие компиляторы. Я предполагаю, что это связано с тем, что (A) ограничение никогда не предназначалось разработчиками языка, или (B), имеющее способ для std::function, который отбрасывает возвращаемые значения.

std::function никогда не требовал точного соответствия аргументов или возвращаемых значений, просто совместимости. Если входящие аргументы могут неявно конвертировать из аргументов подписи, а тип возвращаемого значения может неявно преобразовать в тип возврата, он был счастлив.

И функция типа int(int&) под множеством интуитивных определений совместима с сигнатурой void(int&), поскольку вы можете запускать ее в "пустом контексте".


1 В принципе, все, что делает operator() законным для вызова, не разрешено. Вы можете создать его, вы можете его уничтожить, вы можете проверить его (и знать, что он пуст). Вы не можете дать ему функцию, даже такую, которая точно соответствует своей сигнатуре, или объект функции или лямбда. Смешной.

2 Для void для преобразования impliclty в void в соответствии со стандартом требуется, чтобы оператор void x = blah;, где blah был выражением типа void, допустим; этот оператор недействителен, так как вы не можете создать переменную типа void.