Я создаю класс шаблона, который должен иметь возможность хранить значение или ссылку. Я создаю библиотеку для совместимости с C++11.
Вот соответствующий код для класса
template<typename T>
class Foo
{
private:
T _Data;
public:
Foo(const T& i_Data) :
_Data(i_Data)
{}
virtual ~Foo()
{}
Foo(const Foo &i_Other):
_Data(i_Other._Data)
{};
Foo(Foo &&io_Other):
_Data(std::move(io_Other._Data))
{}
// etc..
};
Код работает, за исключением случаев создания объекта Foo, следующим образом:
double Bar = 2.5;
Foo<double&> f = Bar; // This does not compile (in c++11, C++20 works though)
// error: cannot bind non-const lvalue reference of type ‘double&’ to an rvalue of type ‘std::remove_reference<double&>::type’ {aka ‘double’}
Foo<double&> f(Bar); // This does compile
Я понял, что при использовании первой строки компилятор пытается создать временный Foo, а затем переместить его в Foo, хотя я не понимаю, почему и как этого избежать (конечно, я мог бы удалить конструктор перемещения, но это банальная поправка)
Кроме того, почему он работает с C++17, а не с C++11. Насколько я вижу, это не проблема, связанная с выводом шаблонов, поэтому в обоих стандартах это должно работать одинаково, верно?
Подумайте о ссылке как об альтернативном способе записи «указателя на» и подумайте, что произойдет, когда ваша переменная выйдет за пределы области видимости.
невозможно воспроизвести godbolt.org/z/c5oxMWxbc, и немного подозрительно, что в ошибке упоминается std::remove_reference
, которого нет в коде
@KungPhoo в этом примере ничего не выходит за рамки.
@ 463035818_is_not_an_ai Вы не указали, что стандартом является C++11. Когда вы это сделаете, он не компилируется, и std::remove_reference появляется так, как он вызывается внутри функции std::move.
@ 463035818_is_not_an_ai ошибка существует в C++ 11 и 14, неясно, почему и хотя дизайн OP спорен;), интересен вопрос о том, какое изменение между 14 и 17 заставляет код работать
@Эрстед, ок, кто-то испортил мой божий болт;). Я изменил C++20 в вопросе на C++17, потому что это ввело в заблуждение. Я ожидал увидеть ошибку до C++20 (т.е. также с 17).
@Oersted Да, это был вопрос. Более того, я, конечно, удалил много материала из курса, но если у вас есть советы о том, что можно сделать лучше, я открыт для предложений. Я сделал это таким образом, потому что, насколько я помню из стандарта, когда вы пишете ``` T& Type```, а затем явно сообщаете компилятору, что T является ссылочным типом, например double&, в C++11 компилятор будет рассматривать double& & как базовый ссылочный тип double&. Если я ошибаюсь, можете ли вы указать мне на соответствующую страницу cppreference?
@KungPhoo, конечно, со временем все выходит за рамки, но поскольку Bar
и Foo
находятся в стеке, определенном в таком порядке, сначала будет уничтожен Foo
, затем Bar
. Это единственный случай, когда ссылочный элемент подходит, по моему мнению, как недолговечная оболочка (в полном коде я ожидаю, что копирование и перемещение будут удалены, но это выходит за рамки вопроса)
Вы можете увидеть ответ Яна Шультке. Чтобы свернуть ссылку: en.cppreference.com/w/cpp/language/reference . Вы также можете выполнить различные виды инициализации на C++ ( en.cppreference.com/w/cpp/language/initialization ). Они имеют разное поведение по отношению к конструкциям другого типа. И, таким образом, ответ действительно «копирование элизии», я думаю (я упустил это из виду, когда недавно публиковал вопрос на эту тему...).
В Foo<double&>
конструктор, принимающий const T&
, будет иметь тип, которого вы, вероятно, не ожидали, в результате схлопывания ссылок. См. также Что такое правила свертывания ссылок и как они используются стандартной библиотекой C++?
Вы формируете «константную ссылку lvalue на ссылку lvalue на double
», которая сворачивается в «ссылку на double
», поэтому ваш конструктор фактически:
Foo(double& i_Data)
Таким образом,
Foo<double&> f = Bar;
... совершенно нормально, поскольку Bar
— это lvalue.
До C++17 в этом случае не было обязательного исключения копирования, и приведенная выше строка неявно конвертировала Bar
в Foo<double&>
, а затем использовала конструктор перемещения, но ваш конструктор перемещения не обрабатывал T = double&
должным образом.
Вы пытаетесь связать член _Data
(типа double&
) с double&&
, возвращаемым std::move
, и это не разрешено.
Компилятору было разрешено исключить эту копию и вызов конструктора перемещения, но, по крайней мере на бумаге, вызов должен быть действительным.
Начиная с C++17, инициализация копирования просто вызывает Foo(const T&)
напрямую и не создает никаких временных объектов, поэтому конструктор перемещения не имеет значения.
Однако у вас могут возникнуть проблемы, если вы действительно попытаетесь использовать конструктор перемещения, как в C++11.
Запретить использование ссылок на Foo
. Ситуация становится действительно странной, когда происходит свертывание ссылок, и, скорее всего, поддерживать случай, когда T
является ссылкой, скорее всего, будет больше проблем, чем пользы.
Также обратите внимание, что _Data
— зарезервированное имя. См. также Каковы правила использования подчеркивания в идентификаторе C++?
Хорошо, спасибо за объяснение. Что касается рекомендации, то я застрял, так как мне действительно нужно это для работы со ссылками (на самом деле это единственная причина, по которой мне это нужно). Для контекста мне нужно, чтобы все мои переменные хранились в непрерывном массиве символов где-то в памяти, и делегировали построение «Менеджеру», который затем возвращал бы ссылку на построенные данные, которые затем были бы сохранены в объекте. Как бы вы предложили мне сделать это по-другому? (он должен быть общим)
Деталей недостаточно, чтобы сделать какое-либо конкретное предложение, и раздел комментариев — не лучшее место для его обсуждения. Лучшее место — codereview.stackexchange.com. Вам следует разместить там сообщение и предоставить всю необходимую информацию в новом сообщении.
Хм, я не совсем понимаю, что ты говоришь о схлопывании внутри конструктора. Foo(const T& i_Data)
эквивалентно Foo(T const & i_Data)
, что, ИМХО, более понятно. Тогда T
является double&
дающим Foo(double& const & i_Data)
, а ссылки неизменяемы, поэтому мы можем удалить const
: Foo(double&& i_Data)
. Почему в этом случае произойдет коллапс ссылок?
@Oersted Ссылки lvalue на ссылки lvalue не превращаются в ссылки rvalue. Результата нет &&
. Происходит свертывание ссылок, поскольку в системе типов не существует такой вещи, как ссылка на ссылку, поэтому стандарт говорит, во что вместо этого превращается этот тип.
Спасибо, моя вина: это не копирование и вставка (что дало бы ссылку на rvalue), а взятие ссылки на ссылку (как вы сказали), которая не существует, и свертывание в простую ссылку (первый пример здесь). Понятно (пока я не пролистаю это снова позже ;-6)
С помощью
Foo<double&>
вы говорите, что тип шаблонаT
— это типdouble&
. Это означает, что конструктор принимает ссылку на ссылку наdouble
в качестве аргумента (double& &
), что на самом деле не имеет смысла.