Почему для передачи инициализированного фигурной скобкой временного адреса по адресу требуется явное приведение к тому же типу в MSVS

Я пытался сделать свой код менее раздутым при работе с Windows API, заменив двухстрочные, мало чем отличающиеся

TEMP t{0,1,2}; // let's say it's struct TEMP {int a; int b; int c}
SomeVeryVerboseFunctionName(&t);

с остротами

SomeVeryVerboseFunctionName(&TEMP{0,1,2});

но наткнулся на ошибку:

выражение должно быть lvalue или указателем функции.

После многих попыток я наконец придумал код, который компилируется (MSVS 2013u4):

SomeVeryVerboseFunctionName(&(TEMP) TEMP{0,1,2});//explicit cast to the same type!

Чтобы лучше понять, зачем нужен бросок, я создал простой тестовый проект:

#include <stdio.h>

struct A
{
    int a;
    int b;
    A(int _a, int _b) : a(_a), b(_b) {};
};

struct B
{
    int a;
    int b;
};

template <typename T> void fn(T* in)
{
    printf("a = %i, b = %i\n", in->a, in->b);
}

int main()
{
    fn(&A{ 1, 2 });      //OK, no extra magick
    /*  fn(&B {3, 4});      //error: expression must be an lvalue or function designator */
    fn(&(B)B{ 3, 4 });  //OK with explicit cast to B (but why?)
}

и обнаружил, что если какая-то структура T имеет явный конструктор (как A в приведенном выше коде), то можно взять адрес инициализированного фигурными скобками временного типа T и передать его функции, которая принимает указатель T*, но если у него его нет (например, B), то возникает указанная ошибка, и ее можно преодолеть только путем явного приведения к типу T.

Итак, вопрос: почему B требует такого странного приведения, а A нет?

Обновлять

Теперь, когда ясно, что обработка rvalue как lvalue является расширением/функцией/ошибкой в ​​MSVS, кто-нибудь хочет притвориться, что это на самом деле функция (достаточно используемая MS для ее поддержки с 2010 года), и уточнить, почему временные файлы A и B должны передаваться разными способами, чтобы удовлетворить компилятор? Это должно быть как-то связано с конструктором A и его отсутствием у B...


person sunny moon    schedule 08.07.2015    source источник
comment
Разве вы не получаете адрес временной ошибки? Попробуйте скомпилировать другим компилятором.   -  person BЈовић    schedule 08.07.2015
comment
Примечание. MSVC имеет расширение, которое позволяет результату приведения быть lvalue, похоже, это та же проблема.   -  person Shafik Yaghmour    schedule 08.07.2015
comment
фп(&А{ 1, 2 }); фн(&В {3, 4}); fn(&(B)B{ 3, 4 }); все не так   -  person kiviak    schedule 08.07.2015
comment
@BЈовић только что попробовал с g++ (GCC) 4.9.3 и получил testbench.cpp:23:13: error: taking address of temporary [-fpermissive] fn(&A {1, 2}); и testbench.cpp:25:17: error: taking address of temporary [-fpermissive] fn(&(B) B {3, 4});   -  person sunny moon    schedule 09.07.2015
comment
@ShafikYaghmour: кажется, да. Но почему тогда fn(&A{ 1, 2 }) компилируется? A здесь не кастуется...   -  person sunny moon    schedule 09.07.2015
comment
Это просто получение адреса временного, то есть другого расширения MSVC, которое позволяет привязывать временные файлы к неконстантным ссылкам.   -  person Shafik Yaghmour    schedule 09.07.2015
comment
@ShafikYaghmour: спасибо! Тем не менее я озадачен тем, что, хотя A и B отличаются только конструкторами, функции, принимающие временные значения их типов, могут быть скомпилированы благодаря разным расширениям MSVC. Какая фундаментальная разница между A и B заставляет компилятор обращаться с ними по-разному? Извините, я совсем потерялся.   -  person sunny moon    schedule 09.07.2015


Ответы (2)


template<class T>
T& as_lvalue(T&& t){return t;}
// optional, blocks you being able to call as_lvalue on an lvalue:
template<class T>
void as_lvalue(T&)=delete;

решит вашу проблему, используя легальный C++.

SomeVeryVerboseFunctionName(&as_lvalue(TEMP{0,1,2}));

в некотором смысле as_lvalue является обратным-move. Вы могли бы назвать это unmove, но это бы запутало.

Взятие адреса rvalue недопустимо в C++. Вышеприведенное превращает rvalue в lvalue, после чего получение адреса становится законным.

Причина, по которой использование адреса rvalue является незаконным, заключается в том, что такие данные должны быть отброшены. Указатель будет оставаться действительным только до конца текущей строки (за исключением rvalue, созданного с помощью приведения lvalue). Такие указатели полезны только в крайних случаях. Однако в случае с Windows API многие такие API используют указатели на структуры данных для управления версиями в стиле C.

С этой целью они могут быть безопаснее:

template<class T>
T const& as_lvalue(T&& t){return t;}
template<class T>
T& as_mutable_lvalue(T&& t){return t;}
// optional, blocks you being able to call as_lvalue on an lvalue:
template<class T>
void as_lvalue(T&)=delete;
template<class T>
void as_mutable_lvalue(T&)=delete;

потому что более правильная версия возвращает const ссылку на данные (почему вы изменяете временную?), а более длинная (следовательно, с меньшей вероятностью будет использоваться) возвращает не-const версию.

У MSVC есть старая «ошибка»/«функция», в которой многие вещи рассматриваются как lvalue, когда это не следует делать, включая результат приведения типов. Используйте /Za, чтобы отключить это расширение. Это может привести к тому, что в противном случае работающий код не сможет скомпилироваться. Это может даже привести к тому, что рабочий код перестанет работать и все равно будет компилироваться: я не доказал обратного.

person Yakk - Adam Nevraumont    schedule 08.07.2015
comment
Добавлено предостережение @dyp, обратите внимание, что API-интерфейсы Windows делают их использование более разумным, чем в большинстве случаев, также добавлено mutable изменение. - person Yakk - Adam Nevraumont; 08.07.2015
comment
Для новичка, которым я безнадежно являюсь, определенно полезно знать, как это сделать на законных основаниях. Я должен признать, что очень заманчиво использовать мою первоначальную нелегальную версию, поскольку она такая лаконичная и просто работает, но я еще даже не начал осознавать количество причуд, скрытых в C++. Спасибо! - person sunny moon; 09.07.2015
comment
Отсутствие /Za у @sunnymoon опасно. Для развлечения посмотрите на это: rextester.com/LDWW10898 -- отключите /Za, и (double)d=0.0; перестанет компилироваться. - person Yakk - Adam Nevraumont; 09.07.2015

То, что вы делаете, на самом деле незаконно в C++.

Clang 3.5 жалуется:

23 : error: taking the address of a temporary object of type 'A' [-Waddress-of-temporary]
fn(&A {1, 2}); //OK, no extra magick
   ^~~~~~~~~

25 : error: taking the address of a temporary object of type 'B' [-Waddress-of-temporary]
fn(&(B) B {3, 4}); //OK with explicit cast to B (but why?)
   ^~~~~~~~~~~~~

Все операнды & должны быть lvalues, а не временными. Тот факт, что MSVC принимает эти конструкции, является ошибкой. Согласно ссылке, указанной Шафиком выше, кажется, что MSVC ошибочно создает для них значения lvalue.

person George Hilliard    schedule 08.07.2015
comment
Мне нравится, как они это исправили, и правильное поведение скрывается за флагом чтобы не сломать устаревший код. - person George Hilliard; 08.07.2015