Идеальная пересылка в конструкторах (С++ 17)
Рассмотрим следующий код
struct A {
A(int id) : id_ { id } {}
A(const A& rhs) { std::cout << "cctor from " +
std::to_string(rhs.id_) << std::endl; }
A(A&& rhs) { std::cout << "mctor from " +
std::to_string(rhs.id_) << std::endl; }
int id_;
};
template<typename T>
struct B1 {
constexpr B1(T&& x) noexcept : x_ { std::forward<T>(x) } {}
T x_;
};
template<typename T>
struct B2 {
constexpr B2(T&& x) noexcept;
T x_;
};
template<typename T>
constexpr
B2<T>::B2(
T&& x
) noexcept :
x_ { std::forward<T>(x) } {
}
int
main(
) {
A a { 1 };
//B1 b11 { a }; // Not compiling
B1 b12 { A { 2 } };
B2 b21 { a };
B2 b22 { A { 3 } };
return 0;
}
который дает
mctor from 2
mctor from 3
Таким образом, в основном это выглядит так, как будто внешне определенный конструктор прекрасно перенаправляет категорию значения своего аргумента, а встроенный конструктор - нет.
Это то, что внешне определенный конструктор обрабатывается как шаблон функции (который прекрасно передает свои аргументы) или что здесь происходит?
Ссылки на соответствующий раздел стандарта приветствуются.
Я использую GCC 7.2.0.
Ответы
Ответ 1
Это ошибка GCC. Пересылочные ссылки имеют очень четкое определение:
[temp.deduct.call] (выделено мое)
3 Ссылка на пересылку - это rvalue-ссылка на cv-неквалифицированный параметр шаблона , который не представляет параметр шаблона шаблона класса (во время вывода аргумента шаблона класса ([over.match.class.deduct])). Если P является ссылкой для пересылки, а аргумент является lvalue, тип "lvalue ссылка на A" используется вместо A для вывода типа.
В обоих случаях T
называет параметр шаблона класса включения во время CTAD, поэтому он не должен создавать ссылку для пересылки в любом случае. C'or, определяемый внутри или вне определения класса, не имеет к этому никакого отношения.
Ответ 2
Похоже, что GCC неправильно обрабатывает T&&
в автоматически сгенерированном руководстве по вычетам как ссылку для пересылки:
template <typename T>
B2(T&& x) -> B2<T>;
В этом случае T&&
- это неоправданная ссылка на r-значение, потому что это параметр класса. Вместо этого GCC неправильно выводит тип параметра T=A&
и тип класса B2<T>=B2<A&>
<A &>, что приводит к сворачиванию ссылочного типа в конструкторе, что позволяет скомпилировать код с аргументом конструктора lvalue:
constexpr B2(A& x) noexcept;
Вывод аргумента шаблона класса не делает различий между встроенными и внешними определениями. В данном конкретном случае B2 b21 { a };
должен потерпеть неудачу.