Why is passing a temporary address over a bracket bound binding requires explicit casting of the same type in MSVS

I was trying to make my code less bloated when working with the windows API by replacing two-line characters not like

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

      

with one layer

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

      

but came across an error:

expression must be an lvalue or function designator.

After many attempts, I finally came up with a code that compiles ( MSVS 2013u4 ):

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

      


To better understand why a cast is needed, I created a simple test project:

#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?)
}

      

and it turned out that if any struct T

has an explicit constructor (for example, it A

does in the above code), then it is possible to take the address of the temporary type copied with the type T

and pass it to a function that takes a pointer T*

, but if it does not have one (for example, B

), then the specified error occurs and can only be overcome by explicit casting to the type T

.

So the question is, why B

is such a weird casting required and A

not?

Update

Now that it is clear that treating an rvalue as an lvalue is an extension / feature / bug in MSVS, would anyone want to pretend it is actually a feature (enough for MS to support it since 2010) and elaborate on why temp values A

and B

needs to be passed in different ways to satisfy the compiler? It should have something to do with the constructor, and B should have something to do with it ...

+3


source to share


2 answers


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;

      

will solve your problem using legal C ++.

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

      

in a sense, as_lvalue

is the opposite - move

. You could name it unmove

, but it would be confusing.

Taking an rvalue address is illegal in C ++. The above turns r into an lvalue, after which the address becomes legal.



The reason it is illegal to obtain an rvalue address is that such data must be discarded. The pointer will only remain valid until the end of the current line (disallowing creation of rvalues โ€‹โ€‹with a cast lvalue). Such pointers are only useful in the corner. However, in the case of Windows APIs, many such APIs accept pointers to data structures for C-style versioning purposes.

To this end, they can be safer:

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;

      

because the sooner it is correct, the higher const

the data reference (why are you changing the temporary?) and the longer one (hence less likely to be used) returns the version const

.

MSVC has an old "bug" / "feature" where it treats many things as lvalues โ€‹โ€‹when they shouldn't, including the result of a cast. Use /Za

to disable this extension. This can result in the production code not compiling. This can cause the working code to fail and still compile: I haven't proven otherwise.

+5


source


What you are doing is actually illegal in C ++.

Clang 3.5 complains:



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?)
   ^~~~~~~~~~~~~

      

All operands &

must be lvalues
, rather than temporary. The fact that MSVC accepts these constructs is a mistake. According to the link noted by Shafik above, it seems like MSVC is mistakenly creating lvalues โ€‹โ€‹for them.

+12


source







All Articles