Устанавливает ли std:: function неявный приведение из ссылки для копирования в свой тип возврата?

В приведенном ниже коде компилятор молча переключает указатель функции возврата по копиям в функцию std:: return-by-const-reference. Когда вызывается экземпляр std:: function, возвращается ссылка на копию, и приложение вылетает (большую часть времени;).

Посредством сравнения обычные указатели функций не допускают этот неявный листинг, поэтому я задаюсь вопросом, следует ли мне с жалобой на поставщика компилятора (gcc 4.8 в этом случае), или это поведение задано стандартом?

#include <iostream>
#include <functional>

typedef std::function<const std::string&(const std::string& x)> F;

std::string bad(const std::string& x) { return x; }
const std::string& good(const std::string& x) { return x; }

typedef const std::string& (*FP)(const std::string&);

int main(int, char**) {
    std::cout << F(&good)("hello") << std::endl;
    std::cout << F(&bad)("hello") << std::endl;

    FP a = &good;
    // FP b = &bad;  Not allowed!

    return 0;
}

P.S. Это упрощенная версия проблемы реального мира, где bad была фактически лямбдой, возвращающей член некоторого типа:

typedef std::function<const std::string&(const X& x)> F;
F f = [](const X& x) { return x->member(); };

Нам потребовалось некоторое время, чтобы выяснить, что тип возврата этой лямбды был выведен на std::string, а не const std::string&, и это вызвало сбой.

Ответ 1

Это выглядит как угловой случай. Определение конструктора в п. 2.8.11.2.1/7 гласит:

Требуется: F должно быть CopyConstructible. F должен быть Callable (20.8.11.2) для типов аргументов ArgTypes и возвращаемый тип R. [...]

В §2.8.11.2/2 говорится:

Вызываемый объект F типа F доступен для типов аргументов ArgTypes и возвращает тип R, если выражение expres- sion INVOKE (f, declval<ArgTypes>()..., R), рассматриваемый как неоцениваемый операнд (п. 5), хорошо (20.8.2).

и последний §20.8.2/2 гласит:

Определите INVOKE (f, t1, t2,..., tN, R) как INVOKE (f, t1, t2,..., tN), неявно преобразованные в R.

Очевидно, что T неявно преобразуется в T const &, и поэтому в отсутствие дальнейшего ограничения конструктор должен быть разрешен.

Однако вызов такой функции связан с возвратом ссылки на временную жизнь, заканчивающуюся до того, как ссылка будет возвращена, т.е. Undefined Поведение. И когда что-то есть Undefined Behavior, реализация может делать все, что угодно. К сожалению, поведение Undefined происходит только при вызове, поэтому оно все еще не строго соответствует обнаружению его во время построения.

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

В любом случае, я рекомендую поднять его в соответствующем списке рассылки gcc. Сторонники согласны немного отклониться от спецификации в случае, если это так или, по крайней мере, они могут повысить или помочь вам поднять вопрос с помощью комитета С++, поскольку они работают с ним регулярно.