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

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


person Jacek Sieka    schedule 25.04.2014    source источник
comment
x->member() возвращает std::string или const std::string&?   -  person Stas    schedule 25.04.2014
comment
const std::string&, но для целей вывода типа возвращаемого значения не имеет большого значения, ссылка это или копия — она будет выведена как копия (распадающийся тип) (см. akrzemi1.wordpress.com/2012/03/27/gotchas-of-type-inference)   -  person Jacek Sieka    schedule 25.04.2014
comment
Вы неправильно назвали типы в последнем абзаце? Или я неправильно понял вопрос?   -  person Lightness Races in Orbit    schedule 25.04.2014
comment
Надо было добавить, что F немного отличается в реальном мире — теперь, надеюсь, лучше ;)   -  person Jacek Sieka    schedule 28.04.2014


Ответы (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, если выражение 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 Behavior, реализация может делать все, что ей заблагорассудится. К сожалению, неопределенное поведение происходит только при вызове, поэтому его обнаружение во время создания все еще не является строгим соответствием.

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

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

person Jan Hudec    schedule 25.04.2014
comment
Реализации технически не могут блокировать это, поскольку до вызова не возникает неопределенного поведения... Невычисленное выражение правильно сформировано. Это дефект стандарта. - person Yakk - Adam Nevraumont; 25.04.2014
comment
@Yakk: Вы правы, похоже, это проблема спецификации. - person Jan Hudec; 25.04.2014
comment
Вы знаете, было ли это исправлено в последних стандартах? - person OznOg; 19.07.2021