Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hint compiler to return a reference making 'auto' behave

Tags:

c++

c++14

(possibly related to How to implement a C++ method that creates a new object, and returns a reference to it which is about something different, but incidentially contains almost exactly the same code)

I would like to return a reference to a static local from a static function. I can get it to work, of course, but it's less pretty than I'd like.

Can this be improved?

The background

I have a couple of classes which don't do much except acquire or initialize a resource in a well-defined manner and reliably, and release it. They don't even need to know an awful lot about the resource themselves, but the user might still want to query some info in some way.
That's of course trivially done:

struct foo { foo() { /* acquire */ } ~foo(){ /* release */ } };

int main()
{
    foo foo_object;
    // do stuff
}

Trivial. Alternatively, this would work fine as well:

#include <scopeguard.h>
int main
{
    auto g = make_guard([](){ /* blah */}, [](){ /* un-blah */ });
}

Except now, querying stuff is a bit harder, and it's less pretty than I like. If you prefer Stroustrup rather than Alexandrescu, you can include GSL instead and use some concoction involving final_act. Whatever.

Ideally, I would like to write something like:

int main()
{
    auto blah = foo::init();
}

Where you get back a reference to an object which you can query if you wish to do that. Or ignore it, or whatever. My immediate thought was: Easy, that's just Meyer's Singleton in disguise. Thus:

struct foo
{
//...
    static bar& init() { static bar b; return b; }
};

That's it! Dead simple, and perfect. The foo is created when you call init, you get back a bar that you can query for stats, and it's a reference so you are not the owner, and the foo automatically cleans up at the end.

Except...

The issue

Of course it couldn't be so easy, and anyone who has ever used range-based for with auto knows that you have to write auto& if you don't want surprise copies. But alas, auto alone looked so perfectly innocent that I didn't think of it. Also, I'm explicitly returning a reference, so what can auto possibly capture but a reference!

Result: A copy is made (from what? presumably from the returned reference?) which of course has a scoped lifetime. Default copy constructor is invoked (harmless, does nothing), eventually the copy goes out of scope, and contexts are released mid-operation, stuff stops working. At program end, the destructor is called again. Kaboooom. Huh, how did that happen.

The obvious (well, not so obvious in the first second!) solution is to write:

auto& blah = foo::init();

This works, and works fine. Problem solved, except... except it's not pretty and people might accidentially just do it wrong like I did. Can't we do without needing an extra ampersand?

It would probably also work to return a shared_ptr, but that would involve needless dynamic memory allocation and what's worse, it would be "wrong" in my perception. You don't share ownership, you are merely allowed to look at something that someone else owns. A raw pointer? Correct for semantics, but... ugh.

By deleting the copy constructor, I can prevent innocent users from running into the forgot-& trap (this will then cause a compiler error).

That is however still less pretty than I would like. There must be a way of communicating "This return value is to be taken as reference" to the compiler? Something like return std::as_reference(b);?

I had thought about some con trick involving "moving" the object without really moving it, but not only will the compiler almost certainly not let you move a static local at all, but if you manage to do it, you have either changed ownership, or with a "fake move" move-constructor again call the destructor twice. So that's no solution.

Is there a better, prettier way, or do I just have to live with writing auto&?

like image 814
Damon Avatar asked Sep 06 '16 13:09

Damon


People also ask

Does auto deduce reference?

A copy is being made because auto doesn't deduce references. Variables declared by using auto are never deduced to be type references. If you initialize an auto variable from the result of a function that returns by reference, it results in a copy.

How to make a copy of a reference c++?

C++ gives you the choice: use the assignment operator to copy the value (copy/value semantics), or use a pointer-copy to copy a pointer (reference semantics). C++ allows you to override the assignment operator to do anything your heart desires, however the default (and most common) choice is to copy the value.

Why copy constructor returns reference?

An object is always passed by reference to a copy constructor because if we send the value (call by value method) then in order to copy data from actual argument to formal argument it will call copy constructor, i.e it will call itself again and this process goes on leading to memory insufficiency.


1 Answers

Something like return std::as_reference(b);?

You mean like std::ref? This returns a std::reference_wrapper<T> of the value you provide.

static std::reference_wrapper<bar> init() { static bar b; return std::ref(b); }

Of course, auto will deduce the returned type to reference_wrapper<T> rather than T&. And while reference_wrapper<T> has an implicit operatorT&, that doesn't mean the user can use it exactly like a reference. To access members, they have to use -> or .get().

That all being said however, I believe your thinking is wrong-headed. The reason is that auto and auto& are something that every C++ programmer needs to learn how to deal with. People aren't going to make their iterator types return reference_wrappers instead of T&. People don't generally use reference_wrapper in that way at all.

So even if you wrap all of your interfaces like this, the user still has to eventually know when to use auto&. So really, the user hasn't gained any safety, outside of your particular APIs.

like image 152
Nicol Bolas Avatar answered Oct 13 '22 08:10

Nicol Bolas