Почему передача инициализированного скобкой временного по адресу требует явного приведения к тому же типу в 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 в этом...
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, после чего получение адреса становится законным.
Причина, по которой взятие адреса r-значения является незаконным, заключается в том, что такие данные должны быть отброшены. Указатель останется действительным только до конца текущей строки (за исключением того, что r-значение создается с помощью преобразования l-значения). Такие указатели имеют смысл только в углу. Однако в случае 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 есть старая "ошибка"/"функция", где он обрабатывает многие вещи как l-значения, когда это не должно, включая результат приведения. использование /Za
отключить это расширение. Это может привести к сбою компиляции работающего кода. Это может даже вызвать сбои в работе рабочего кода и его компиляцию: я не доказал обратное.
То, что вы делаете, на самом деле незаконно в 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?)
^~~~~~~~~~~~~
Все операнды &
должны быть значения, а не временные. Тот факт, что MSVC принимает эти конструкции, является ошибкой. По ссылке, указанной Шафиком выше, кажется, что MSVC ошибочно создает lvalues для них.