Pass pointer to temporary in c++ 11?
Asked Answered
J

7

13

I have an existing function:

void foo(const Key* key = nullptr)
{
  // uses the key
}

I want to pass it pointer to temporary Key object (i.e. rvalue) like:

foo(&Key());

This causes compilation error, but is there a way in c++ 11/14 how I can do this? Of course I could do:

Key key;
foo(&key);

But I don't need object Key, I only need it inside foo() and foo()

Or I could do:

foo(new Key());

But then the object will not be deleted.

Johannejohannes answered 23/11, 2017 at 14:51 Comment(10)
How about adding void foo(const Key &key){ foo(&key); }?Maeda
Are you using 3 different standards of c++ at once?Carabiniere
You already answered your own question. You have to create a new Key and delete it afterwards or use a smart pointer.Reeta
I know that, but I don't understand why in this specific case the language does not allow me to do what is absolutely natural.Johannejohannes
@Reeta I don't see a need to bring dynamic allocation into this.Stefanstefanac
@AndreyRubliov: Because it isn't natural, and it shouldn't be.Deuterium
@Nicol, the object Key is only used inside foo() right? Why do I need to declare it before, so it will live outside the scope of foo() ?Johannejohannes
Just pass nullptr, or omit providing an argument.Hayman
@AndreyRubliov: What's not natural is passing a pointer to a temporary.Deuterium
There's nothing wrong with passing a const pointer to a temporary in this type of context. It's safe, well defined and avoids introducing unnecessary named temporaries.Polycythemia
O
11

I don't think this is a good idea, but if you really really want a temporary and cannot change foo, you can cast the temporary to a const&:

int main()
{
    foo(&static_cast<const Key&>(Key{}));
}

live example on wandbox


Alternatively, you could "hide" the creation of the object behind a convenient function:

template <typename T, typename F, typename... Ts>
void invoke_with_temporary(F&& f, Ts&&... xs)
{
    T obj{std::forward<Ts>(xs)...};
    f(&obj);
}

int main()
{
    invoke_with_temporary<Key>(&foo);
}

live example on wandbox.org


Another alternative: provide an overload of foo that takes a reference:

void foo(Key&& key)
{
    foo(&key);
}
Orthopteran answered 23/11, 2017 at 14:53 Comment(11)
Will it result in undefined behavior?Johannejohannes
@AndreyRubliov The temporary will exist until the end of the expression, which will be after foo returns. It's address will remain valid for the entirety of foo. What would be undefined behavior would be to store that pointer and to use it after the temporary ceased to exist.Stefanstefanac
you guys are correct. it's not UB, but it's still extremely hackish. I would never pass such code under a code reviewSplanchnology
@DavidHaim I don't see it that way. But it seems like many users share your opinion about this. I'm curious what part seems hackish? You can wrap this cast in a template function to hide it.Stefanstefanac
it's hackish to resort to some nasty, ambiguous, non intuitive memory games where you can simply avoid it altogether. it appears like the OP is fixated on this very specific technique instead of just fixing the function signature or provide an overload. it's about insisting going in the wrong directions, and advanced developers who don't realize that their knowledge can be destructive for beginners.Splanchnology
@DavidHaim I guess nasty is subjective. But there is are no ambiguities or "memory games" being played. I can agree that an overload would be a better solution to OP's problem and this answer would be improved by proposing it. But this post does answer the question as it was asked.Stefanstefanac
@DavidHaim: literally the first sentence in my answer is "I don't think this is a good idea, but if you really really want a temporary and cannot change foo, you can cast the temporary to a const&"...Orthopteran
"I don't think it's a good idea, but If you really want to drink this poison, do it with this antidote". it's still a bad advice, the only answer to this kind of questions is "no, don't"Splanchnology
@DavidHaim: I disagree - that's not answering the question. OP's question is clear and has an answer, which I provided. Whether or not that's a good idea is irrelevant. It obviously is good practice to mention that in the answer.Orthopteran
This is gorgeous in a "holy smokes thats evil" sorta way.Breathed
It's not dangerous and IMO understanding object lifetimes enough that it is obvious that it is not dangerous is a requirement for a C++ programmer. In my answer I give a real world example where it is useful and the approach I use to make the cast less ugly looking.Polycythemia
A
6

This is a utilty function. It is basically the inverse of std::move1:

template<class T>
T& as_lvalue( T&& t ) { return t; }

if used wrong it can lead to dangling references.

Your code then becomes:

foo(&as_lvalue(Key()));

the goal of "cannot take an address of a temporary" is because you can otherwise get extremely unexpected behaviour due to things like implicit temporary creation.

In this case, we are explicitly taking the address of a temporary.

It is no more dangerous than creating a named value, calling the function, and then discarding the named value immediately.


1 std::move takes an l or r value and returns a rvalue reference to it, indicating that consumer should treat it as a temporary whose existence will be shortly discarded. as_lvalue takes an l or r value reference and returns an lvalue reference to it, indicating that the consumer should treat it as a non-temporary whose existence will persist.

They are both valid operations, but std::move is more crucial. (std::move could be called as_rvalue really). I'd advise against clever names like unmove.

Administer answered 23/11, 2017 at 16:39 Comment(5)
I like this but I wonder if there's something to be said for making it const T& as_lvalue(const T&& t)? That won't compile with a non rvalue but why are you using this if you already have an lvalue? It also makes it clearer that you shouldn't be using this in non-const contexts. Or am I missing something?Polycythemia
@Polycythemia I see nothing wrong with using it in a non-const context. The result is discarded, but nothing goes wrong. Sometimes you really just want to have a value that is writable and can be discarded without having to create a named variable. std::move works on rvalues and on const lvalues, as_lvalue works on lvalues, const or not, as well as on rvalues, const or not.Administer
I agree there's nothing wrong with it, I'm just not sure how useful it is in practice. I guess a function that takes a non-optional out parameter that you aren't interested in? My use case for this type of thing is mostly functions taking non-optional input parameters by const pointer.Polycythemia
@Polycythemia A function takes an optional out parameter by pointer, and it does different things if it exists or not so you want to pass it in. Or it takes a non-optional out parameter by pointer and you want to ignore it. Or it does the same by reference. Or you are talking to a legacy API that takes a non-const pointer in an in parameter. Plus it works for optional in-parameters. All these cases will be lost in time, like tears in the rain.Administer
Ok, you make a compelling case :) I think I'll use &as_lvalue() in future over my temp_ptr() approach described in my answer.Polycythemia
P
5

Just control the scope of the throw-away variable yourself:

{
    Key key;
    foo(&key);
} // <-- 'key' is destroyed here
Portal answered 23/11, 2017 at 15:0 Comment(0)
D
3

The only reason to take a pointer rather than a reference is if the pointer can be null. And if that's the case, then your foo function will look something like this:

void foo(const Key *key)
{
  if(key)
    //Do stuff with `key`
  else
    //Alternate code
}

Given that, what you want is a second overload, one that refactors all of the "do stuff with key" into its own function that takes a reference. So do that:

void foo(const Key &key)
{
  //Do stuff with `key`
}

void foo(const Key *key)
{
  if(key)
    foo(*key);
  else
    //Alternate code.
}

If you have common code that gets executed in both cases, refactor that out into its own function too.

Yes, it's possible that "do stuff with key" is complicated and is split up into several lines. But that suggests a very strange design here.

You could also refactor it the other way:

void foo(const Key &key) {foo(&key);}
Deuterium answered 23/11, 2017 at 15:1 Comment(3)
I'd agree it's poor API design but there are many existing legacy APIs that some of us still have to work with that take const T* arguments even for non-optional arguments. See my DirectX 11 examples in my answer for example. Writing wrappers for all those APIs is not always practical.Polycythemia
@mattnewport: But your D3D example leads to exceedingly ugly code. If a function call statement requires 10+ lines, then something is very wrong. Basically, most D3D structs shouldn't be passed as temporaries; they have too many members.Deuterium
The D3D API design unfortunately lends itself to ugly code but I still have to work with it. Many of the D3D 'parameter structs' have sensible defaults that apply in many circumstances (which is why they provide the convenience CD3D11_XXX helper classes) or they have particular (parameterized) combinations that represent common uses like an index buffer. If I want to write a helper function that returns those I still need something like temp_ptr()Polycythemia
P
3

I use something like this:

template <typename T>
const T* temp_ptr(const T&& x) { return &x; }

Used like this:

foo(temp_ptr(Key{}));

This is very useful when dealing with certain legacy APIs. DirectX 11 in particular frequently takes parameter aggregating structs by const T* and it's convenient to create and pass them inline. I don't think there's anything wrong with this idiom unlike some of the commenters here, although I'd prefer if those APIs just took a const reference and handled optional arguments differently.

Here's an example D3D11 API call where this is very useful:

    vector<Vec3f> verts;
    ...
    ID3D11BufferPtr vbuf;
    d3dDevice->CreateBuffer(
        temp_ptr(CD3D11_BUFFER_DESC{byteSize(verts), D3D11_BIND_VERTEX_BUFFER}),
        temp_ptr(D3D11_SUBRESOURCE_DATA{data(verts)}), &vbuf);

For calling ID3D11Device::CreateBuffer() to create a vertex buffer.

On larger projects I might write wrappers for many of the D3D API calls which make them more convenient to call in a modern C++ style but for small standalone sample projects that I want to have minimum extra code or dependencies I find this very useful.

Another trick I've used in the past that works is:

foo(std::data({Key{}}));

But I don't particularly recommend this as I think the intent is unclear and relies on a bit too much knowledge of how initializer lists work. A variation is useful if you need to pass a temporary 'array' though:

d3dDeviceContext->ClearRenderTargetView(rendertarget, data({0.0f, 0.0f, 0.0f, 0.0f}));

For calling an API like ID3D11DeviceContext::ClearRenderTargetView().

Polycythemia answered 23/11, 2017 at 15:53 Comment(0)
B
2

if what you concern is variable scope (as you mention in comment) you can use

{Key key;foo(&key);}

Becket answered 23/11, 2017 at 15:0 Comment(0)
B
-2

Or I could do:

foo(new Key());

But then the object will not be deleted.

You could create a smart pointer, but I'd rather not. It still is a possibility though.

#include <memory>
foo(std::make_unique<const Key>().get());
Beers answered 23/11, 2017 at 14:56 Comment(4)
@Veedrac There are no memory leak. The temporary smart pointer will live until the full expression is evaluated, and the memory allocated to the created const Key will then be freed.Beers
Bah, mixing up my methods. Still, heap allocation here is silly.Broker
@Broker That's exactly what I say.Beers
So why does this answer exist?Broker

© 2022 - 2024 — McMap. All rights reserved.