10

I thought it would be useful to be able to initialize a struct that references another struct with a pointer, using the designated initializer. Such a pattern often happens in some APIs (e.g., Vulkan). For instance, consider the following structs:

struct A {
    int v;
};

struct B {
    int v;
    A* a;
};

struct C {
    B* b;
};

and a function taking C as an argument

void func(const C& c) {}

I came up with an approach using unmove() (ref) function that converts rvalue to lvalue:

template <typename T>
T& unmove(T&& v) { return v; }

Using this function, the nested initialization can be written by

func({
    .b = &unmove(B{
        .v = 1,
        .a = &unmove(A{
            .v = 2
        })
    })
});

My question: Is it a valid usage of the function in terms of the standard? Or am I missing something?

cigien
  • 57,834
  • 11
  • 73
  • 112
perimasu
  • 1,025
  • 1
  • 8
  • 16
  • 4
    Those temporaries exist until the `;` then all the pointers become dangling - read the complete answer you linked, especially _"...if the function returns a reference, which outlives the full expression, it becomes a dangling reference...."_. – Richard Critten Jun 19 '21 at 20:41
  • Does this answer your question? [Rvalue to lvalue conversion?](https://stackoverflow.com/questions/44677825/rvalue-to-lvalue-conversion) – Richard Critten Jun 19 '21 at 23:18
  • 1
    I think a bigger consideration in this case is other programmers (or yourself at a later date) wondering what on earth is going on -- it would be hard to endorse this even if legal – M.M Jun 20 '21 at 00:58

2 Answers2

6

This is safe as long as the function doesn't store or return the referred object or its address or the nested pointers for later usage. The temporary objects will be destroyed at the end of the full expression and as such the mentioned stored / returned references / pointers would be invalid.

[class.temporary]

... Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created. ...

eerorika
  • 232,697
  • 12
  • 197
  • 326
2

I'd make it addressof_rvalue:

template <typename T>
T* addressof_rvalue(T&& v) { return std::addressof(v); }
template <typename T>
T* addressof_rvalue(T& v) = delete;

it is safe, so long as the pointer doesn't outlive the function you are calling.

func({
    .b = addressof_rvalue(B{
        .v = 1,
        .a = addressof_rvalue(A{
            .v = 2
        })
    })
});
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • The name is a bit confusing. My initial interpretation is that the function "converts a pointer into an rvalue". Although, with second though I can see that the returned pointer points to an rvalue. – eerorika Jun 22 '21 at 19:46