MSVC: A non-const reference may only be bound to an lvalue
От: пффф  
Дата: 23.07.23 06:48
Оценка: :))
Привет

Есть код:

typedef std::set<unsigned>  UnsignedSet;

inline
UnsignedSet& operator<<(UnsignedSet &us, unsigned u)
{
    us.insert(u);
    return us;
}

void serializeUnsignedSet(const UnsignedSet &us);

//...

serializeUnsignedSet(UnsignedSet() << 2u << 10u); //!!!


Выдаёт при /Wall:
Non-standart extention used: 'argument': conversion from 'UnsignedSet' to 'UnsignedSet&'
A non-const reference may only be bound to an lvalue


Как я понимаю, ему не нравится то, что operator<< получает временный объект по ссылке?

Как это всё правильно переписать?
Re: MSVC: A non-const reference may only be bound to an lvalue
От: so5team https://stiffstream.com
Дата: 23.07.23 07:14
Оценка: 2 (1) +1
Здравствуйте, пффф, Вы писали:

П>Есть код:

П>

П>typedef std::set<unsigned>  UnsignedSet;

П>inline
П>UnsignedSet& operator<<(UnsignedSet &us, unsigned u)
П>{
П>    us.insert(u);
П>    return us;
П>}

П>void serializeUnsignedSet(const UnsignedSet &us);

П>//...

П>serializeUnsignedSet(UnsignedSet() << 2u << 10u); //!!!
П>


П>Как это всё правильно переписать?


Например, добавить еще одну перегрузку для operator<<
inline
UnsignedSet&& operator<<(UnsignedSet &&us, unsigned u)
{
    us.insert(u);
    return std::move(us);
}//...
Re[2]: MSVC: A non-const reference may only be bound to an lvalue
От: пффф  
Дата: 23.07.23 07:29
Оценка:
Здравствуйте, so5team, Вы писали:


П>>Как это всё правильно переписать?


S>Например, добавить еще одну перегрузку для operator<<

S>
S>inline
S>UnsignedSet&& operator<<(UnsignedSet &&us, unsigned u)
S>{
S>    us.insert(u);
S>    return std::move(us);
S>}//...
S>


А, точно, всё время забываю про новые возможности новых стандартов
Re: MSVC: A non-const reference may only be bound to an lvalue
От: rg45 СССР  
Дата: 24.07.23 22:24
Оценка:
Здравствуйте, пффф, Вы писали:

П>Привет


П>Есть код:

П>

П>typedef std::set<unsigned>  UnsignedSet;

П>inline
П>UnsignedSet& operator<<(UnsignedSet &us, unsigned u)
П>{
П>    us.insert(u);
П>    return us;
П>}

П>void serializeUnsignedSet(const UnsignedSet &us);

П>//...

П>serializeUnsignedSet(UnsignedSet() << 2u << 10u); //!!!
П>


П>Выдаёт при /Wall:

П>
Non-standart extention used: 'argument': conversion from 'UnsignedSet' to 'UnsignedSet&'
П>A non-const reference may only be bound to an lvalue
П>


П>Как я понимаю, ему не нравится то, что operator<< получает временный объект по ссылке?


П>Как это всё правильно переписать?


template <typename T>
T& lvalue(T&& t) { return t; }

//...

serializeUnsignedSet(lvalue(UnsignedSet()) << 2u << 10u); //!!!
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: MSVC: A non-const reference may only be bound to an lvalue
От: Sm0ke Россия ksi
Дата: 26.07.23 09:47
Оценка: +1
Здравствуйте, so5team, Вы писали:

S>Например, добавить еще одну перегрузку для operator<<

S>
S>inline
S>UnsignedSet&& operator<<(UnsignedSet &&us, unsigned u)
S>{
S>    us.insert(u);
S>    return std::move(us);
S>}//...
S>


Разве это не приведёт к dangling reference? Параметр функции всё равно что локальная переменная. Вы предлагаете вернуть ссылку на локальный объект.
Мне кажется тут надо по значению возвращать, оставив однако мув в return

inline
UnsignedSet operator << (UnsignedSet && us, unsigned u)
{
    us.insert(u);
    return std::move(us);
}
Re[3]: MSVC: A non-const reference may only be bound to an lvalue
От: so5team https://stiffstream.com
Дата: 26.07.23 10:07
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Разве это не приведёт к dangling reference? Параметр функции всё равно что локальная переменная.


Не думаю. "Все равно что локальная переменная" -- это когда у нас параметр передается по значению, т.е.:
int & f(int a) {
  return a;
}

В случае же когда параметр передается по ссылке, мы просто возвращаем эту же самую ссылку и на нее действуют обычные правила жизни.

Когда мы имеем дело с rvalue reference, то rvalue reference должно оставаться валидным в течении жизни выражения, в котором эта ссылка появилась. Т.е.:
T && operator<<(T && d, int v) { ...; return std::move(d); }

(T{} << 0) << 1;

сперва у нас создается временный объект T и rvalue reference на него передается в первый вызов operator<<. Он эту же самую ссылку возвращает. И эта ссылка остается валидной, т.к. выражение у нас не завершилось. Когда вызывается второй operator<<, то эта ссылка отдается туда же. И там она опять же валидна, т.к. выражение все еще не завершилось.
Re[2]: MSVC: A non-const reference may only be bound to an lvalue
От: _NN_ www.nemerleweb.com
Дата: 26.07.23 10:26
Оценка:
Здравствуйте, so5team, Вы писали:

П>>Как это всё правильно переписать?


S>Например, добавить еще одну перегрузку для operator<<

S>
S>inline
S>UnsignedSet&& operator<<(UnsignedSet &&us, unsigned u)
S>{
S>    us.insert(u);
S>    return std::move(us);
S>}//...
S>


А зачем здесь нужен std::move ?
std::move всего лишь приводит тип к UnsignedSet&&, который и так у нас есть.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[3]: MSVC: A non-const reference may only be bound to an lvalue
От: so5team https://stiffstream.com
Дата: 26.07.23 10:31
Оценка: 9 (1)
Здравствуйте, _NN_, Вы писали:

_NN>А зачем здесь нужен std::move ?

_NN>std::move всего лишь приводит тип к UnsignedSet&&, который и так у нас есть.

https://wandbox.org/permlink/opM2owLr51OIDWaM

prog.cc: In function 'UnsignedSet&& operator<<(UnsignedSet&&, unsigned int)':
prog.cc:16:12: error: cannot bind rvalue reference of type 'UnsignedSet&&' {aka 'std::set<unsigned int>&&'} to lvalue of type 'UnsignedSet' {aka 'std::set<unsigned int>'}
   16 |     return us; //std::move(us);
      |            ^~
Re[3]: MSVC: A non-const reference may only be bound to an l
От: Videoman Россия https://hts.tv/
Дата: 26.07.23 12:02
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Разве это не приведёт к dangling reference?


Нет. Вот что думает на это счёт стандартная библиотека:
template<class T> 
const T& max(const T& a, const T& b)
{
    return (a < b) ? b : a;
}
Отредактировано 26.07.2023 12:03 Videoman . Предыдущая версия .
Re[4]: MSVC: A non-const reference may only be bound to an l
От: Sm0ke Россия ksi
Дата: 26.07.23 12:53
Оценка:
Здравствуйте, so5team, Вы писали:

S>В случае же когда параметр передается по ссылке, мы просто возвращаем эту же самую ссылку и на нее действуют обычные правила жизни.


S>Когда мы имеем дело с rvalue reference, то rvalue reference должно оставаться валидным в течении жизни выражения, в котором эта ссылка появилась. Т.е.:

S>
S>T && operator<<(T && d, int v) { ...; return std::move(d); }

S>(T{} << 0) << 1;
S>

S>сперва у нас создается временный объект T и rvalue reference на него передается в первый вызов operator<<. Он эту же самую ссылку возвращает. И эта ссылка остается валидной, т.к. выражение у нас не завершилось. Когда вызывается второй operator<<, то эта ссылка отдается туда же. И там она опять же валидна, т.к. выражение все еще не завершилось.

Вы ошибаетесь. Рассмотрим пример:

#include <utility>
#include <iostream>

struct obj_t {
    bool f = false;
    
    obj_t() { std::cout << "obj_t default\n"; }
    ~obj_t() { std::cout << "~obj_t\n"; }

    obj_t(obj_t &&) { std::cout << "obj_t move\n"; }
    obj_t(const obj_t &) { std::cout << "obj_t copy\n"; }
};

obj_t && some_fn(obj_t && o) { return std::move(o); } // bad

int main() {
  obj_t && tmp = some_fn( obj_t{} ); // tmp битая ссылка
  std::cout << "going to return\n";
  return 0;
}


Результат:

obj_t default
~obj_t
going to return


Время жизни временного объекта продлевается при привязке его на rvalue reference локальной переменной (или параметра) и откладывается до момента, когда переменная выйдет из scope. В данном случае время жизни параметра o заканчивается после выхода из функции some_fn() .
Иначе пример выше выдал бы сперва "going to return" из main(), а потом "~obj_t".

Тобишь когда выражение { obj_t && tmp = some_fn( obj_t{} ); } отработает, то tmp будет dangling ref.
При бинде одной rvalue ref от другой rvalue ref время жизни временного объекта дальше не продлевается. Только при бинде от самого временного объекта.

Корректный пример:

#include <utility>
#include <iostream>

struct obj_t {
    bool f = false;

    obj_t() { std::cout << "obj_t default\n"; }
    ~obj_t() { std::cout << "~obj_t\n"; }

    obj_t(obj_t &&) { std::cout << "obj_t move\n"; }
    obj_t(const obj_t &) { std::cout << "obj_t copy\n"; }
};

obj_t some_fn(obj_t && o) { return std::move(o); } // good

int main() {
  obj_t && tmp = some_fn( obj_t{} );
  std::cout << "going to return\n";
  return 0;
}


Результат:

obj_t default
obj_t move
~obj_t
going to return
~obj_t


К сожалению объект класса obj_t тут будет создан дважды.
Сперва default constructor, потом move constructor, потом дважды destructor.

В случае вектора — мув конструктор считается относительно дешёвым.
Отредактировано 26.07.2023 12:54 Sm0ke . Предыдущая версия .
Re[5]: MSVC: A non-const reference may only be bound to an l
От: so5team https://stiffstream.com
Дата: 26.07.23 12:56
Оценка: +1
Здравствуйте, Sm0ke, Вы писали:

S>>Когда мы имеем дело с rvalue reference, то rvalue reference должно оставаться валидным в течении жизни выражения, в котором эта ссылка появилась. Т.е.:

S>>
S>>T && operator<<(T && d, int v) { ...; return std::move(d); }

S>>(T{} << 0) << 1;
S>>

S>>сперва у нас создается временный объект T и rvalue reference на него передается в первый вызов operator<<. Он эту же самую ссылку возвращает. И эта ссылка остается валидной, т.к. выражение у нас не завершилось. Когда вызывается второй operator<<, то эта ссылка отдается туда же. И там она опять же валидна, т.к. выражение все еще не завершилось.

S>Вы ошибаетесь. Рассмотрим пример:


S>
S>  obj_t && tmp = some_fn( obj_t{} ); // tmp битая ссылка
S>


А это ничего, что у нас два разных примера?
Re[4]: MSVC: A non-const reference may only be bound to an l
От: Sm0ke Россия ksi
Дата: 26.07.23 13:01
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Нет. Вот что думает на это счёт стандартная библиотека:
V>template<class T> 
V>const T& max(const T& a, const T& b)
V>{
V>    return (a < b) ? b : a;
V>}
V>


Чтож, читаем notes по вашей ссылке:

Notes
Capturing the result of std::max by reference produces a dangling reference if one of the parameters is a temporary and that parameter is returned:

int n = 1;
const int& r = std::max(n — 1, n + 1); // r is dangling


При передаче в эту функцию временного объекта и при привязке по const ref результата будет провисшая ссылка на уже разрушенный объект.
Так что будьте осторожней со стандартным максом
Re[4]: MSVC: A non-const reference may only be bound to an lvalue
От: _NN_ www.nemerleweb.com
Дата: 26.07.23 13:12
Оценка:
Здравствуйте, so5team, Вы писали:

S>Здравствуйте, _NN_, Вы писали:


_NN>>А зачем здесь нужен std::move ?

_NN>>std::move всего лишь приводит тип к UnsignedSet&&, который и так у нас есть.

S>https://wandbox.org/permlink/opM2owLr51OIDWaM


S>
S>prog.cc: In function 'UnsignedSet&& operator<<(UnsignedSet&&, unsigned int)':
S>prog.cc:16:12: error: cannot bind rvalue reference of type 'UnsignedSet&&' {aka 'std::set<unsigned int>&&'} to lvalue of type 'UnsignedSet' {aka 'std::set<unsigned int>'}
S>   16 |     return us; //std::move(us);
S>      |            ^~
S>


Действительно.
Не задумывался об этом совсем.
Получается в С++ как только есть именнованный объект он сразу lvalue и нужно явно его возвращать в rvalue.

struct A {};
void f(A&& a)
{
    A&& b = a;
}


<source>(4): error C2440: 'initializing': cannot convert from 'A' to 'A &&'
<source>(4): note: You cannot bind an lvalue to an rvalue reference
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[5]: MSVC: A non-const reference may only be bound to an l
От: so5team https://stiffstream.com
Дата: 26.07.23 13:13
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Чтож, читаем notes по вашей ссылке:


S>

S>Notes
S>Capturing the result of std::max by reference produces a dangling reference if one of the parameters is a temporary and that parameter is returned:

S>int n = 1;
S>const int& r = std::max(n — 1, n + 1); // r is dangling


S>При передаче в эту функцию временного объекта и при привязке по const ref результата будет провисшая ссылка на уже разрушенный объект.

S>Так что будьте осторожней со стандартным максом

Это да. Но поинт в том, что пока std::max работает, у вас валидная ссылка. Иначе бы вот такой вот пример не работал бы:
#include <string>
#include <iostream>

using namespace std::string_literals;

int main()
{
    auto v = std::max(std::max(std::max("One"s, "Two"s), "Three"s), "Zero"s);
    std::cout << v << std::endl;
}

Но он работает.

Потому что в выражении, где std::max вызываются, ссылки валидные. Но когда выражение завершается, уничтожаются все временные объекты, созданные в процессе его выполнения, поэтому ссылка и протухает.

Именно из-за этого ссылку нельзя сохранять. Но вот передать ее в конструктор объекта (как в примере) можно, т.к. она все еще валидная.
Re[6]: MSVC: A non-const reference may only be bound to an l
От: Sm0ke Россия ksi
Дата: 26.07.23 14:03
Оценка:
Здравствуйте, so5team, Вы писали:

S>Это да. Но поинт в том, что пока std::max работает, у вас валидная ссылка. Иначе бы вот такой вот пример не работал бы:

S>
S>#include <string>
S>#include <iostream>

S>using namespace std::string_literals;

S>int main()
S>{
S>    auto v = std::max(std::max(std::max("One"s, "Two"s), "Three"s), "Zero"s);
S>    std::cout << v << std::endl;
S>}
S>

S>Но он работает.

S>Потому что в выражении, где std::max вызываются, ссылки валидные. Но когда выражение завершается, уничтожаются все временные объекты, созданные в процессе его выполнения, поэтому ссылка и протухает.


S>Именно из-за этого ссылку нельзя сохранять. Но вот передать ее в конструктор объекта (как в примере) можно, т.к. она все еще валидная.


Понял, спасибо.

Но вот в чём могут быть подводные камни с предложением сделать так из исходной задачи

typedef std::set<unsigned>  UnsignedSet;

inline
UnsignedSet&& operator<<(UnsignedSet &&us, unsigned u)
{
    us.insert(u);
    return std::move(us);
}


Результат оператора << в таком виде нельзя напрямую передать в range for

for(
  unsigned it : (UnsignedSet{} << 2u << 10u)
) {}


И придётся писать более длинно, чтобы ссылка не провисла

for(
  UnsignedSet res = (UnsignedSet{} << 2u << 10u) // не rvalue, а move constructor
  unsigned it : res
) {}


В случае с UnsignedSet operator<< использовать проще, не опасаясь не правильно использовать либу.
Отредактировано 26.07.2023 14:06 Sm0ke . Предыдущая версия . Еще …
Отредактировано 26.07.2023 14:04 Sm0ke . Предыдущая версия .
Re: MSVC: A non-const reference may only be bound to an lvalue
От: Кодт Россия  
Дата: 10.08.23 23:54
Оценка:
Здравствуйте, пффф, Вы писали:

П>Как это всё правильно переписать?


Я конечно дико извиняюсь, но, может, лучше написать функцию, которая конструирует множество по значению из списка аргументов (россыпью или initializer_list)?
И дать ей имя какое-нибудь другое, вместо неконстантного "оператора вдвига".

Тем более, что std::set умеет принимать список
serializeUnsignedSet(UnsignedSet{1, 2, 3, 4, 5});
Перекуём баги на фичи!
Re[2]: MSVC: A non-const reference may only be bound to an lvalue
От: пффф  
Дата: 12.08.23 10:38
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Я конечно дико извиняюсь, но, может, лучше написать функцию, которая конструирует множество по значению из списка аргументов (россыпью или initializer_list)?

К>И дать ей имя какое-нибудь другое, вместо неконстантного "оператора вдвига".

К>Тем более, что std::set умеет принимать список

К>
К>serializeUnsignedSet(UnsignedSet{1, 2, 3, 4, 5});
К>



В принципе да, можно. Я про списки инициализации периодически забываю, а способ с operator<< украден из кути
Re[3]: MSVC: A non-const reference may only be bound to an lvalue
От: T4r4sB Россия  
Дата: 12.08.23 11:22
Оценка:
Здравствуйте, _NN_, Вы писали:


_NN>А зачем здесь нужен std::move ?

_NN>std::move всего лишь приводит тип к UnsignedSet&&, который и так у нас есть.

Это так не работает
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

struct T {
  T() { std::cout << "T()\n"; }
  ~T() { std::cout << "~T()\n"; }
  T(const T&) { std::cout << "T(const T&)\n"; }
  T& operator=(const T&) { std::cout << "=(const T&)\n"; return *this; }
  T(T&&) { std::cout << "T(T&&)\n"; }
  T& operator=(T&&) { std::cout << "=(T&&)\n"; return *this; }
};

void check(T&& t) {
    std::cout << "1\n";
    T t1 = t;
    std::cout << "2\n";
    T t2 = std::move(t);
    std::cout << "3\n";
}

int main() {
    check(T());
    return 0;
}



T()
1
T(const T&)
2
T(T&&)
3
~T()
~T()
~T()


Знаешь, зачем придумали std::forward, кстати?
Re[4]: MSVC: A non-const reference may only be bound to an lvalue
От: reversecode google
Дата: 12.08.23 11:30
Оценка:
TB>Знаешь, зачем придумали std::forward, кстати?

для зачета достаточно сказать кто скажем так, открыл и ввел move семантику)))
Re[5]: MSVC: A non-const reference may only be bound to an l
От: B0FEE664  
Дата: 15.08.23 14:07
Оценка:
Здравствуйте, Sm0ke, Вы писали:
  Скрытый текст
S>Вы ошибаетесь. Рассмотрим пример:
S>
S>#include <utility>
S>#include <iostream>

S>struct obj_t {
S>    bool f = false;
    
S>    obj_t() { std::cout << "obj_t default\n"; }
S>    ~obj_t() { std::cout << "~obj_t\n"; }

S>    obj_t(obj_t &&) { std::cout << "obj_t move\n"; }
S>    obj_t(const obj_t &) { std::cout << "obj_t copy\n"; }
S>};

S>obj_t && some_fn(obj_t && o) { return std::move(o); } // bad

S>int main() {
S>  obj_t && tmp = some_fn( obj_t{} ); // tmp битая ссылка
S>  std::cout << "going to return\n";
S>  return 0;
S>}
S>


S>Результат:


S>
S>obj_t default
S>~obj_t
S>going to return
S>

S>Время жизни временного объекта продлевается при привязке его на rvalue reference локальной переменной (или параметра) и откладывается до момента, когда переменная выйдет из scope. В данном случае время жизни параметра o заканчивается после выхода из функции some_fn() .
Нет, время жизни заканчивается после полного выполнения выражения obj_t && tmp = some_fn( obj_t{} );, т.е. после присвоения ссылки. Но вы и сами это понимаете:

S>Иначе пример выше выдал бы сперва "going to return" из main(), а потом "~obj_t".

S>Тобишь когда выражение { obj_t && tmp = some_fn( obj_t{} ); } отработает, то tmp будет dangling ref.
S>При бинде одной rvalue ref от другой rvalue ref время жизни временного объекта дальше не продлевается. Только при бинде от самого временного объекта.
S>Корректный пример:

Во это тоже корректный пример:
obj_t tmp = some_fn( obj_t{} ); // tmp не бита, не ссылка

  Скрытый текст
S>
S>#include <utility>
S>#include <iostream>

S>struct obj_t {
S>    bool f = false;

S>    obj_t() { std::cout << "obj_t default\n"; }
S>    ~obj_t() { std::cout << "~obj_t\n"; }

S>    obj_t(obj_t &&) { std::cout << "obj_t move\n"; }
S>    obj_t(const obj_t &) { std::cout << "obj_t copy\n"; }
S>};

S>obj_t some_fn(obj_t && o) { return std::move(o); } // good

S>int main() {
S>  obj_t && tmp = some_fn( obj_t{} );
S>  std::cout << "going to return\n";
S>  return 0;
S>}
S>


S>Результат:


S>
S>obj_t default
S>obj_t move
S>~obj_t
S>going to return
S>~obj_t
S>

Причём с тем же результатом:
S>К сожалению объект класса obj_t тут будет создан дважды.
S>Сперва default constructor, потом move constructor, потом дважды destructor.

S>В случае вектора — мув конструктор считается относительно дешёвым.


Но, если посмотреть дальше, то можно заметить пессимизацию при возвращении объекта.
Рассмотри выражение:
some_fn(some_fn(some_fn( obj_t{} )));

Если some_fn возвращает объект, то при каждом выходе из some_fn будет создаваться новый объект, если же some_fn возвращает ссылку, то пересоздания объекта не будет вовсе. Это может быть важно при использовании operator<<

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