Почему передача инициализированного скобкой временного по адресу требует явного приведения к тому же типу в 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 ​​для них.

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