С++: invalid initialization of non-const reference of type 'foo&' from an rvalue of type 'foo'

Вот представим себе такой вот зачин:

class foo
{
public:
 foo(int aa, int bb) : a(aa),b(bb){}
 int A() { return a;}
 int B() { return b;}
private:
 int a,b;
};

int func(foo& f) { return f.A()*f.B();}
Почему передача по reference? Ну это, типа, дешевле. Для структуры из двух полей вероятно плевать, а если там std::string содержащий "Войну и Мир"? При передаче по значению оно же должно скопироваться?

А теперь пытаемся это использовать:

int main()
{
    return func(foo(1,2));
}
g++ (4.6, если важно) ругается и говорит:
error: invalid initialization of non-const reference of type 'foo&' from an rvalue of type 'foo'
А Visual Studio 2010 жрет нормально. При этом такой вот код, естественно, нормально жрется и gcc:
int main()
{
    {
    foo f(1,2);
    return func(f);
    }
}
Вторые фигурные скобки - исключительно для указания локальности контекста, в котором живет foo f;

Внимание, вопрос: а что, собственно не нравится gcc?. Семантически, как я понимаю, разницы вообще никакой, на стеке создается объект типа foo, потом сразу помирает. В чем разница, почему gcc запрещает первый случай?

Про const понимаю, но опять же какая разница, объект на стеке сразу помрет или циклом позже?

Comments

> Семантически, как я понимаю, разницы вообще никакой, на стеке создается объект типа foo, потом сразу помирает. В чем разница,
> почему gcc запрещает первый случай?

func(foo(1,2)) все равно что:

const foo f(1,2);
func(f);

А func берет не константную ссылку.
Вот так будет работать:
class foo
{
public:
foo(int aa, int bb) : a(aa),b(bb){}
int A() const { return a;}
int B() const { return b;}
private:
int a,b;
};

int func(const foo& f) { return f.A()*f.B();}

int main(int argc, char *argv[]) {
return func(foo(1, 2));
}

Не, меня волнует смысл этого всего. Это сделано для того, чтобы нельзя было бы сделать вот так:

foo& func(foo& a) { return a; }

?

Или для чего?

Ааа. В стандарте наверняка написано точно, но мне кажется, что раз ф-ция берет не константную ссылку, то компилятор думает что она будет объект менять, а раз он временный то скорее всего девелопер допустил ошибку. Кто ж будет чего то менят, и не смотреть результат :)

Ну reference-то non-const. То есть теоретически функция func может пытаться объект foo изменить.
Соответственно, если туда передана переменная, локальная в каком-то контексте, то компилятор имеет право надеяться, что в следующей строке после вызова эти изменения будут использованы.

Он же на строку вперед не смотрит и не видит, что после вызова переменная сразу выходит из scope.
Вот если параметр был бы const reference, тогда было бы понятно, что изменять его функция все равно не будет, и можно было бы его на стеке создавтаь.

А отследить что этот объект вообще неизменяемый - это ни у одного компилятора интеллекта не хватит, тем более, что автор описания объекта не пометил все public методы как const

То есть вопрос тут не вида "на стеке или где мы там создаем объект", а в высокоуровневых терминах "функция теоретически может поменять этот объект, поэтому нельзя передавать туда объект, изменения в котором никак не удастся увидеть".

тут скорее не про "видеть", а про старую шутку "изменялили вы значение константы 4?"

Ну казалось бы, warning. Вроде как на unused variable или на значение.

А то прямо удивительное дело: в gcc это (неотключаемый?) error, а в MSVC я даже предупреждение в этом месте не могу включить (или не сумел).

Хуже. В C++ есть перегрузка и неявные преобразования типов. И если разрешить передавать по ссылке временные объекты, то в функцию может уйти совсем не тот объект и совсем не в ту функцию. Молча. С потерей результата.

Собственно, чтобы избежать таких проблем, в C++ и ввели константные ссылки. А дальше константные указатели, методы и т.п.

Видимо, классификацией ошибок по уровню в gcc team давно перестали заниматься, полагая что все все равно используют -Werror.

это не единственное место, где MSVC по-умолчанию ведёт себя более лояльно.
Отключается это в свойствах проекта C/C++ -> Язык -> отключить расширения языка (/Za)
и пример компилируется с error C2664

к сожалению, отключается еще много всего - и перестает варез с Qt собираться по причине ошибок в этом самом Qt (headers)

интересно, а как оно на gcc тогда собирается.

как вариант, если нужно всё-таки отключить - вынести не QT-зависимый код в статическую библиотеку.

Так там все в #ifdef-ах.

Ошибки то не в foo&, а в каких-то невероятных местах.

не, я понимаю что не в foo&, там куча других мест(и не только с errors связаное).
вот boost тоже зафейлился без msvc extensions. видимо эти ifdef закладываются на extensions.

то есть это не баг, а фича, к счастью контролируемая.

Ну так с const - аналогично, может уйти не то и не туда. Не вижу разницы.

Страуструп в своей книге Дизайн и эволюция C++ приводит следующий пример:

void incr(int& rr) { rr++; }

void g()
{
double ss = 1;
incr(ss);
}

Сперва он допустил ошибку, разрешив инициализировать неконстантную ссылку значением, не являющимся lvalue. Требуется ссылка на int, есть double и есть преобразование из double в int. Поэтому создавалась временная переменная типа int, инициализированная значением ss, и в функцию incr передавалась ссылка на эту переменную. Изменялась временная переменная, а значение ss в g оставалось неизменным.

Эту ошибку Страуструп исправил с выходом C++ 2.0 в 1987 году, MSVC поддерживает совместимость с ошибкой, исправленной задолго до выхода компиляторов MS с поддержкой C++, до сих пор.

потому что буква закона!
а временная анонимная переменная видимо создается как const.
что по этому поводу говорит стандарт -- я не знаю.
возможно такое поведение помогает отловить автоматически некоторое количество ошибок.

ну придется тебе три раза написать const в этом примере...

А какого хрена она const, если она временная? Отчего я не могу надругаться над ней как хочу?

ну я же напомнил: "изменяли ли вы в программе значение константы 4?"

ну то есть по аналогии, что bar(int& ); bar(4); звать нельзя?

Интересно, а MSVC это сожрет, надо попробовать....

ты главное в bar эту 4 поменяй.
и два раза вызови, что бы посмотреть что второму достанется.

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

А в стандарте там небось на этом месте undefined behavoir. В микрософте его решили определить так, как тебе кажется логичным, в GCC team по-другому.

Сразу вспоминается ван дер линденовская байка про реакцию на неопознанную #pragma в каком-то раннем gcc - запускать nethack, а если не найден, то rogue, с намеком "если у тебя в коде такое написано, ты сюда не программировать а играться пришел".

а вот кстати, про константу 4.
сколько объектов будет созданно, если func(foo(1,2)); будет употребленно более одного раза?
если один -- это многое объясняет.

таки да (по крайне мере частично)!
только надо влепить синус например, что бы компилятор арифметику сам не посчитал и тогда для кода

int a;
a = func(foo(1,2));
return a+func(foo(1,2));

он сам умножит на два, а func вызовет только один раз.

Это точно?
IMHO, это неправильно. foo может возвращать разное значение при каждом следующем вызове даже при одинаковых параметрах. Во всяком случае раньше MS VC таких оптимизаций не делал и звал 2 раза.

Для полноценных экспериментов надо тело функции прятать в другой единице компиляции (что делать с Link-time code generation вовсе не знаю).

Потому что компиляторы сейчас ОЧЕНЬ умные.

Оптимизации ВСЕ попробуй выключить, тогда интеллекта должно сильно поубавиться.

И вот если оптимизированный бинарь и обычный выдадут разные значения - можно предметно ругаться.

class foo
{
public:
foo(int aa, int bb) : a(aa),b(bb){}
int A() const { return a;}
int B() const { return b;}
private:
int a,b;
};

int func(const foo& f) { return sin(f.A())*f.B();}

int main()
{
int a;
a = func(foo(1,2));
return a+func(foo(1,2));
}

.LCFI9:
.LBB33:
.LBB34:
# t.cc:12
.loc 1 12 0
movl $0, (%esp)
movl $1072693248, 4(%esp)
call sin
fnstcw -6(%ebp)
fadd %st(0), %st
movzwl -6(%ebp), %eax
movb $12, %ah
movw %ax, -8(%ebp)
fldcw -8(%ebp)
fistpl -12(%ebp)
fldcw -6(%ebp)
movl -12(%ebp), %eax
.LVL1:
.LBE34:
.LBE33:
# t.cc:19
.loc 1 19 0
addl $20, %esp
popl %ecx
popl %ebp
.LBB36:
.LBB35:
# t.cc:12
.loc 1 12 0
addl %eax, %eax
.LVL2:
.LBE35:
.LBE36:
# t.cc:19
.loc 1 19 0
leal -4(%ecx), %esp
ret

foo может возвращать разное значение при каждом следующем вызове даже при одинаковых параметрах.

ну в данном случае он видит код foo. пока там синуса не было он вообще ее вызов до двойки оптимизировал и сразу четверку возвращал.

а если __attribute((pure)) написать -- и extern начинает умножать.
а если конструктору pure написать -- он объект перестает инициализировать, кажется.

А, ну да.
Я имею в виду общий случай, когда компилятор не видит кода функции.

Не важно, это вопрос implementation. Важно, что foo(1,2) - это константа.

На пальцах:

void foo( int & a ) { ... }
...
foo( 2 ); // ошибка времени компиляции

Тот самый пример.

А почему foo(1,2) - это обязательно константа? Что может помешать конструктору во внешний мир слазить?

foo(1,2) по стандарту rvalue, по нему же rvalue нельзя привести к lvalue. То есть правильно либо const foo& f, либо foo&& f и -std=c++0x. Это не UB, чтобы варнинги выдавать, gcc совершенно правильно ругается. clang его в этом, кстати, тоже поддерживает, это только msvc отличился, похоже.

Если совсем по-простому, то, судя по прототипу, func(foo&) может изменять аргумент. А foo(1,2) - это константа. Менять константу - плохо. Это не нравится GCC. Мне бы это тоже не понравилось, через code review такое я бы не пропустил (за исключением случаев "голова не варит").

Не-не, эта логика мне кажется ущербной.

foo f(1,2) - объект.
func(foo(1,2) - сразу константа.

Откуда разница?

foo f(1,2) - объект с именем f. Automatic storage переменная.

foo(1,2) - константа. Без какого-либо storage class. Компилятор имеет полное право вообще не создавать никаких объектов для foo(1,2). А может и создать. Implementation dependent.

OK, хорошо, пример с 2 катит, потому что исключения для базовых типов создавать не по понятиям.

Что касается "имеет полное право не создавать" - что-то у меня сомнения. У меня же могут быть в конструкторе сайд-эффекты какие-то? Ну там запуск двигателя третьей ступени..... Посему - конструктор обязан позваться.

Ну и соответственнно, вопрос - безымянный объект (не константу) - никак нельзя создать?

> Ну и соответственнно, вопрос - безымянный объект (не константу) - никак нельзя создать?

Не помню :( Давно не брал в руки шашки, совсем плохой стал.

Должно быть можно, automatic storage class переменной имя не нужно - на то она и automatic, что сама уничтожится, как время выйдет. Как на практике - не помню, совсем плохой стал.

__attribute__ ((pure))

Для структуры из двух полей вероятно плевать, а если там std::string содержащий "Войну и Мир"? При передаче по значению оно же должно скопироваться?

Нет, не должно. std::string крайне не рекомендуется передавать по ссылке, если важна производительность и ссылка реально не нужна. ;-)

Внимание, вопрос: а что, собственно не нравится gcc?. Семантически, как я понимаю, разницы вообще никакой, на стеке создается объект типа foo, потом сразу помирает.

Слушай, ну стандарт! не-конст-ссылка не может указывать на rvalue. Семантически эти два куска очень различны. После вызова func() ты во втором случае можешь использовать f. То, что ты его НЕ ИСПОЛЬЗУЕШЬ -- это частности. Стандарт рассматривает отдельные сиквенс-пойнты. За точку-с-запятой он не заглядывает, и есть там использование f, нету его, или вообще там анричбл-код стандарт не волнует. MSVC++ умничает тут, проверяя что-то ПОСЛЕ оптимизатора. gcc проверять правильность типов ДО оптимизатора и не знает, что там дальше. И он прав.

После вызова func() ты во втором случае можешь использовать f. То, что ты его НЕ ИСПОЛЬЗУЕШЬ -- это частности.

А в первом случае - не могу. Потому что нечего. И что теперь?

Ну то есть идея про унификацию поведения с константами базовых типов - она понятная. Но какая-то дурацкая.

что вот эти самые безымянные объекты компилятор имеет право УБИТЬ, как только завершится scope, который в первом примере - один оператор f(foo(1,2)); Т.е. сработает деструктор и на выходе может получиться хня:

foo& f(foo& ff) { return ff; }

И потом так: foo& a = f(foo(1,2));

Ну и чо? Ты получил имя для того, что имени иметь не должно и вообще уже убито. Будет тебе плохо, если ты это имя как-то начнешь пользовать. Вот поэтому и ввели в стандарт жоское правило: rvalue - константа, и баста!

А МС - любитель надругаться над стандартами и что-то сделать по-своему, чтоб его код - портировался похуже.

Во втором случае (со скобками) будет аналогичный выход из scope.

под вторым случаем:

{ foo a(1,2);
foo& b = f(a);
}

?

Так тут все нормально: у тебя нормальная переменная (lvalue) с четко определенным временем жизни. Выход из scope изничтожит и объект и ссылку на него, нещастья не будет.

Ну, то есть, по человечески твое изумление понятно, но основание объявить результат конструктора rvalue - есть и достаточно обоснованное, основание считать rvalue константой - тоже обосновано. Т.ч. по закону - все правильно, а по сути - издевательство. Но так часто бывает.

Вот в других езыках (Objective C, D) - там этот момент определяется, возможно, по-другому и безымянные объекты имеют право на жизнь.

Ну так можно и вот так:

foo& bar(foo& baz) { return baz; }
...
{
foo var(1,2);
return bar(var);
}

И имеем абсолютно то же говно, но с именоваными переменными. return ссылки - вообще дело такое, опасное.

То есть я не вижу такой уж совсем разницы. По смыслу не вижу, понятно что rvalue, то-се.

Это про вот это:

bar& f()
{ bar a; return a; }

- тут тебе предупреждение (не ошибку) выдадут. А ты предложил, как этот ворнинг затоптать.

Короче, с точки зрения авторов стандарта - это вообще о разных ситуациях.

в том драфте стандарта что у меня этот пункт (8.5.3.5) расчиркан разным цветом вдоль и поперёк :)

Visual Studio 2008:
warning C4239: nonstandard extension used : 'argument' : conversion from 'foo' to 'foo &'
A non-const reference may only be bound to an lvalue

Это level-4 warning, если что...

Сталкивался с этой фигней при портировании под gcc, как я понял, по стандарту все-таки то что делает vs не совсем стандартно. Однако с точки зрения логики и удобства использования непонятно зачем такое ограничение, приходится писать лишние строки кода, вводить переменные.

Это старый известный баг MSVC. Хорошая новость в последних версиях таки можно специальным ключиком заставить его действовать по Стандарту. Плохая по умолчанию это отключено, иначе перестанет собираться куча старого кривого кода, и это ведёт к появлению нового кривого кода.

А какой ключ (VS 2010) ?

Я допер только до /W4, но он кроме предупреждения в обсуждаемом случе генерирует безумную кучу других предупреждений.

Не знаю, я им не пользуюсь уже много лет. Какой-то должен быть, как и для ограничения видимости переменных, объявленных в заголовке цикла (ещё одно достающее отклонение от Стандарта). В крайнем случае должна быть возможность включить не /W4, а конкретное предупреждение. Ну и хоть изредка прогонять код через g++, он найдёт и другие возможные ошибки.

если хочется, то можно использовать


template
T &i_know_what_i_am_doing(const T &params)
{
return const_cast(params);
}

главное не передавать так int'ы, реальные константы и т.п.

В C++11 всё вообще легально(?):

template
T &i_know_what_i_am_doing(T &&params)
{
return params;
}

Кстати, если вызвать у временного объекта метод возвращающий ссылку на *this, то никакие касты не нужны (С++98): http://ideone.com/B92s9

Add new comment