3

I was trying to make my code less bloated when dealing with windows API by replacing two-liners not unlike

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

with one-liners

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

but stumbled upon error:

expression must be an lvalue or function designator.

After many attempts I finally came up with code that does compile (MSVS 2013u4):

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

To better understand why the cast is needed I set up 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 found out that if some struct T has an explicit constructor (like A has in the above code), then it's possible to take address of brace-initialized temporary of type T and pass it to a function that takes a pointer T*, but if it doesn't have one (like B), then the said error arises and can only be overcome by explicit casting to type T.

So the question is: why does B require such strange casting and A doesn't ?

Update

Now that it's clear that treating rvalue as lvalue is an extension/feature/bug in MSVS, does anyone care to pretend it's actually a feature (enough used for MS to maintain it since 2010) and elaborate on why temporaries of A and B need to be passed in different ways in order to satisfy the compiler? It must have something to do with A's constructor and B's lack thereof...

sunny moon
  • 1,313
  • 3
  • 16
  • 30
  • Don't you get "taking address of temporary" error? Try compiling with different compiler. – BЈовић Jul 08 '15 at 15:21
  • 4
    Note MSVC has an extension that allows the [result of cast to be an lvalue](http://stackoverflow.com/a/26508755/1708801) this seems to be the same issue – Shafik Yaghmour Jul 08 '15 at 15:23
  • fn(&A{ 1, 2 }); fn(&B {3, 4}); fn(&(B)B{ 3, 4 }); all wrong – kiviak Jul 08 '15 at 15:36
  • @BЈовић just tried with g++ (GCC) 4.9.3 and got `testbench.cpp:23:13: error: taking address of temporary [-fpermissive] fn(&A {1, 2});` and `testbench.cpp:25:17: error: taking address of temporary [-fpermissive] fn(&(B) B {3, 4});` – sunny moon Jul 08 '15 at 23:15
  • @ShafikYaghmour: seems so. But why does `fn(&A{ 1, 2 })` compile then? `A` is not casted here... – sunny moon Jul 09 '15 at 18:35
  • 1
    That is simply taking the address of a temporary, that is a different MSVC extension which [allows binding temps to non-const references](http://stackoverflow.com/questions/16380966/non-const-reference-bound-to-temporary-visual-studio-bug/20851672#20851672). – Shafik Yaghmour Jul 09 '15 at 18:44
  • @ShafikYaghmour: thank you! Still I'm puzzled that while `A` and `B` differ only in contructors, the functions taking temporaries of their types manage to get compiled by virtue of *different* MSVC extensions. What fundamental difference between `A` and `B` makes the compiler treat them in distinct ways? Sorry, I'm totally lost. – sunny moon Jul 09 '15 at 19:08

2 Answers2

12

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 of & must be lvalues, not temporaries. The fact that MSVC accepts these constructs is a bug. According to the link pointed out by Shafik above, it seems that MSVC erroneously creates lvalues for these.

Community
  • 1
  • 1
George Hilliard
  • 15,402
  • 9
  • 58
  • 96
  • 2
    A [direct link to the bug in question](https://connect.microsoft.com/VisualStudio/Feedback/Details/615622) – Drew Dormann Jul 08 '15 at 15:28
  • 4
    I love how they "fixed" it and the correct behavior is hiding behind a [flag](https://msdn.microsoft.com/en-us/library/dn449507.aspx) to avoid breaking legacy code. – George Hilliard Jul 08 '15 at 15:31
5
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 an inverse-move. You could call it unmove, but that would get confusing.

Taking the address of an rvalue is illegal in C++. The above turns the rvalue into an lvalue, at which point taking the address becomes legal.

The reason why taking an address of an rvalue is illegal is that such data is meant to be discarded. The pointer will only remain valid until the end of the current line (barring the rvalue being created via a cast of an lvalue). Such pointers only have corner-case usefulness. However, in the case of windows APIs, many such APIs take pointers to data structures for C-style versioning purposes.

To that end, these might 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 more likely to be correct one returns a const reference to data (why are you modifying a temporary?), and the longer one (hence less likely to be used) returns the non-const version.

MSVC has an old "bug"/"feature" where it treats many things as lvalues when it should not, including the result of casts. Use /Za to disable that extension. This can cause otherwise working code to fail to compile. It may even cause working code to fail to work and still compile: I have not proved the contrary.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • @dyp caution added, note that windows APIs make using them more reasonable than most cases also added, `mutable` variation added. – Yakk - Adam Nevraumont Jul 08 '15 at 19:07
  • For a beginner which I hopelessly am it's definitely useful to know how to do that legally. I must admit it's very tempting to use my original illegal version since it's so terse and just works, but I'm yet to even begin conceiving the number of quirks concealed within c++. Thanks! – sunny moon Jul 09 '15 at 00:20
  • 1
    @sunnymoon lacking `/Za` is dangerous. For some fun, look at this: http://rextester.com/LDWW10898 -- turn off `/Za` and the `(double)d=0.0;` stops compiling. – Yakk - Adam Nevraumont Jul 09 '15 at 02:11