В приведенном ниже коде компилятор молча преобразует указатель функции return-by-copy в std :: function 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;
}
Постскриптум Это упрощенная версия проблемы реального мира, где bad
был на самом деле лямбда-возвращение члена некоторого типа:
typedef std::function<const std::string&(const X& x)> F;
F f = [](const X& x) { return x->member(); };
Нам потребовалось некоторое время, чтобы выяснить, что возвращаемый тип этой лямбды был выведен std::string
не const std::string&
и что это вызывало аварию.
Это выглядит как своего рода угловой корпус. Определение конструктора в §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
если экспресс-
SionINVOKE (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 &
и поэтому в отсутствие дальнейших ограничений Конструктор должен быть разрешен.
Однако вызов такой функции включает в себя возвращение ссылки на временный объект, чья жизнь заканчивается до того, как ссылка будет возвращена, что является неопределенным поведением. А когда что-то является неопределенным поведением, реализация может делать все, что пожелает. К сожалению, неопределенное поведение происходит только при вызове, поэтому все еще не совсем точно обнаруживать его во время создания.
Поскольку вызывать его — единственное использование объекта, было бы лучше, если бы это было запрещено. Так это следует учитывать дефект в спецификации.
В любом случае, я бы порекомендовал внести это в соответствующий список рассылки gcc. Сопровождающие будут готовы немного отклониться от спецификации в случае, если это так, или, по крайней мере, они могут поднять или помочь вам поднять проблему с комитетом C ++, поскольку они регулярно работают с ним.