Действительно ли возвращаемая единая инициализированная ссылка действительна
Этот пример кода действителен?
using ref = char&;
ref foo(ref x) {
return ref{x};
}
int main() {
char a;
foo(a);
return 0;
}
Кажется, что:
- лязг 3.5 говорит ДА
gcc 4.9 говорит НЕТ
main.cpp: In function 'char& foo(ref)': main.cpp:4:15: error: invalid cast of an rvalue expression of type 'char' to type 'ref {aka char&}' return ref{x}; ^
http://coliru.stacked-crooked.com/a/cb6604b81083393f
Так какой компилятор прав? или это не указано?
Это очень легко, поэтому преодолеть ошибку сборки gcc можно:
используя скобки вместо фигурных скобок
ref foo(ref x) { return ref(x); }
по названию возвращаемого значения
ref foo(ref x) { ref ret{x}; return ret; }
вариант 1. нарушает равномерную инициализацию, вариант 2. добавляет бесполезную строку кода.
Подобный вопрос уже задавался здесь: почему я не могу инициализировать ссылку в списке инициализаторов с равномерной инициализацией?
Но упомянутый pr50025 исправлен в gcc 4.9.
Я знаю, что приведенный выше пример кода довольно бесполезен, но я намеренно упростил его, чтобы указать на проблему. В реальной жизни проблемы кода могут быть скрыты в универсальной функции, такой как:
#include <utility>
template <typename Tp, typename... Us>
Tp bar(Us&&... us) {
return Tp{std::forward<Us>(us)...};
}
1 ответ
Это кажется упущением в стандарте, где GCC реализует именно то, что требует стандарт, а Clang стремится к тому, что, вероятно, и предполагалось.
Из C++11 (выделено мое):
5.2.3 Явное преобразование типов (функциональная запись) [expr.type.conv]
1 Спецификатор простого типа (7.1.6.2) или спецификатор typename (14.6), за которым следует список выражений в скобках, создает значение указанного типа по заданному списку выражений. Если список выражений является одним выражением, выражение преобразования типа эквивалентно (в определенности и если определено в значении) соответствующему приведенному выражению (5.4). [...]
[...]
3 Аналогично, спецификатор простого типа или спецификатор typename, за которым следует braced-init-list, создает временный объект указанного типа direct-list-initialized (8.5.4) с указанным braced-init-list и его значение этого временного объекта в качестве значения.
В случае с фигурным списком инициализации стандарт не указывает, что это работает так же, как приведение в стиле C. И это не так:
typedef char *cp;
int main() {
int i;
(cp(&i)); // okay: C-style casts can be used as reinterpret_cast
(cp{&i}); // error: no implicit conversion from int * to char *
}
К несчастью, T(expr)
эквивалентно (T)expr
это также единственное исключение, в котором функциональное приведение не обязательно приводит к prvalue. Стандарт не может определить аналогичное исключение для функционального преобразования, использующего список фигурных скобок init-list для ссылочного типа. В результате в вашем примере ref{x}
создает временный тип ref
, инициализированный прямым списком из {x}
, Этот временный объект затем обрабатывается как значение prvalue, потому что стандарт говорит, что такое поведение должно быть, и это значение prvalue нельзя использовать для привязки к ссылке lvalue.
Я сильно подозреваю, что если бы этот вопрос был вынесен на рассмотрение комитета ISO C++, стандарт был бы изменен, чтобы требовать поведения clang, но, исходя из текущей формулировки стандарта, я думаю, что это GCC, что правильно, по крайней мере для вашего конкретного примера.
Вместо добавления переменной или перехода в скобки, вы можете опустить ref
(Tp
) чтобы избежать проблемы:
template <typename Tp, typename... Us>
Tp bar(Us&&... us) {
return {std::forward<Us>(us)...};
}