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

В приведенном ниже коде компилятор молча преобразует указатель функции 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&и что это вызывало аварию.

12

Решение

Это выглядит как своего рода угловой корпус. Определение конструктора в §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 если экспресс-
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 & и поэтому в отсутствие дальнейших ограничений Конструктор должен быть разрешен.

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

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

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

6

Другие решения


По вопросам рекламы [email protected]