Создание класса шаблона со ссылкой не компилируется

Я создаю класс шаблона, который должен иметь возможность хранить значение или ссылку. Я создаю библиотеку для совместимости с 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. Насколько я вижу, это не проблема, связанная с выводом шаблонов, поэтому в обоих стандартах это должно работать одинаково, верно?

С помощью Foo<double&> вы говорите, что тип шаблона T — это тип double&. Это означает, что конструктор принимает ссылку на ссылку на double в качестве аргумента (double& &), что на самом деле не имеет смысла.

Some programmer dude 24.04.2024 16:04

Подумайте о ссылке как об альтернативном способе записи «указателя на» и подумайте, что произойдет, когда ваша переменная выйдет за пределы области видимости.

KungPhoo 24.04.2024 16:05

невозможно воспроизвести godbolt.org/z/c5oxMWxbc, и немного подозрительно, что в ошибке упоминается std::remove_reference, которого нет в коде

463035818_is_not_an_ai 24.04.2024 16:06

@KungPhoo в этом примере ничего не выходит за рамки.

463035818_is_not_an_ai 24.04.2024 16:09

@ 463035818_is_not_an_ai Вы не указали, что стандартом является C++11. Когда вы это сделаете, он не компилируется, и std::remove_reference появляется так, как он вызывается внутри функции std::move.

J.M 24.04.2024 16:09

@ 463035818_is_not_an_ai ошибка существует в C++ 11 и 14, неясно, почему и хотя дизайн OP спорен;), интересен вопрос о том, какое изменение между 14 и 17 заставляет код работать

Oersted 24.04.2024 16:09

@Эрстед, ок, кто-то испортил мой божий болт;). Я изменил C++20 в вопросе на C++17, потому что это ввело в заблуждение. Я ожидал увидеть ошибку до C++20 (т.е. также с 17).

463035818_is_not_an_ai 24.04.2024 16:12

@Oersted Да, это был вопрос. Более того, я, конечно, удалил много материала из курса, но если у вас есть советы о том, что можно сделать лучше, я открыт для предложений. Я сделал это таким образом, потому что, насколько я помню из стандарта, когда вы пишете ``` T& Type```, а затем явно сообщаете компилятору, что T является ссылочным типом, например double&, в C++11 компилятор будет рассматривать double& & как базовый ссылочный тип double&. Если я ошибаюсь, можете ли вы указать мне на соответствующую страницу cppreference?

J.M 24.04.2024 16:13

@KungPhoo, конечно, со временем все выходит за рамки, но поскольку Bar и Foo находятся в стеке, определенном в таком порядке, сначала будет уничтожен Foo, затем Bar. Это единственный случай, когда ссылочный элемент подходит, по моему мнению, как недолговечная оболочка (в полном коде я ожидаю, что копирование и перемещение будут удалены, но это выходит за рамки вопроса)

463035818_is_not_an_ai 24.04.2024 16:15
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В 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++?

Хорошо, спасибо за объяснение. Что касается рекомендации, то я застрял, так как мне действительно нужно это для работы со ссылками (на самом деле это единственная причина, по которой мне это нужно). Для контекста мне нужно, чтобы все мои переменные хранились в непрерывном массиве символов где-то в памяти, и делегировали построение «Менеджеру», который затем возвращал бы ссылку на построенные данные, которые затем были бы сохранены в объекте. Как бы вы предложили мне сделать это по-другому? (он должен быть общим)

J.M 24.04.2024 16:23

Деталей недостаточно, чтобы сделать какое-либо конкретное предложение, и раздел комментариев — не лучшее место для его обсуждения. Лучшее место — codereview.stackexchange.com. Вам следует разместить там сообщение и предоставить всю необходимую информацию в новом сообщении.

Jan Schultke 24.04.2024 16:25

Хм, я не совсем понимаю, что ты говоришь о схлопывании внутри конструктора. Foo(const T& i_Data) эквивалентно Foo(T const & i_Data), что, ИМХО, более понятно. Тогда T является double&дающим Foo(double& const & i_Data), а ссылки неизменяемы, поэтому мы можем удалить const: Foo(double&& i_Data). Почему в этом случае произойдет коллапс ссылок?

Oersted 24.04.2024 17:09

@Oersted Ссылки lvalue на ссылки lvalue не превращаются в ссылки rvalue. Результата нет &&. Происходит свертывание ссылок, поскольку в системе типов не существует такой вещи, как ссылка на ссылку, поэтому стандарт говорит, во что вместо этого превращается этот тип.

Jan Schultke 24.04.2024 17:13

Спасибо, моя вина: это не копирование и вставка (что дало бы ссылку на rvalue), а взятие ссылки на ссылку (как вы сказали), которая не существует, и свертывание в простую ссылку (первый пример здесь). Понятно (пока я не пролистаю это снова позже ;-6)

Oersted 24.04.2024 17:27

Другие вопросы по теме