Returning tuple of local objects - c++

How does one exploit structured binding and tuples to return objects local to a function?
In a function, I am making local objects that reference each other, and I want to return these objects in a tuple and use structured binding to identify them whenever I call the function. I currently have this:
std::tuple<Owner&&, State<Controller>&&, State<Ancillary>&&, State<Compressor>&&>
inline makeOwner() {
State<Controller>&& controller = State<Controller>();
State<Ancillary>&& ancillary = State<Ancillary>();
State<Compressor>&& compressor = State<Compressor>();
Owner&& owner = Owner(controller, ancillary, compressor);
return {owner, controller, ancillary, compressor};
}
// using the function later
const &&[owner, controller, ancillary, compressor] = makeOwner();
This does not work, and I get an error saying the return value isn't convertable to a tuple of the aforementioned return type. I'm not sure why this is the case, since the types match up to the declarations.
Ultimately, I'm trying to create a convenience function so I don't have to type the four lines in the function every time I want to make a new Owner. This is my attempt at using structured binding to make this easier.
EDIT:
I should note that I want the bindings in the last line to reference the objects inside of owner. So, copies are insufficient.

I want the bindings in the last line to reference the objects inside of owner.
Let's ignore all of the new language features and go back to basics. How do you expect this to work?
int&& f() { return 0; }
int&& r = f();
You want r to be a reference to the local variable inside of f? But that gets destroyed at the end of the execution of f(). This code compiles but r is a dangling reference.
The only way for this to be safe is to ensure that f() returns a reference to an object that definitely outlives the function. Maybe it's a local static, maybe it's global, maybe it's a member variable of the class that f is a member function of, etc:
int global = 0;
int&& f() { return std::move(global); }
int&& r = f(); // okay, r is a reference to global, no dangling
Or, if that doesn't make sense, then you need to return an object by value. You can still take a reference to it. Or not:
int f() { return 0; }
int&& r = f(); // okay, lifetime extension
int i = f(); // okay, prvalue elision
The same underlying principles apply once we add in all the complexities of tuple and structured bindings. Either return local, non-static objects by value, or return some other objects by reference. But do not return local, non-static objects by reference.
Ultimately, I'm trying to create a convenience function so I don't have to type the four lines in the function every time I want to make a new Owner. This is my attempt at using structured binding to make this easier.
Why not just make a type?
struct X {
X() : owner(controller, ancillary, compressor) { }
X(X const&) = delete;
X& operator=(X const&) = delete;
State<Controller> controller;
State<Ancillary> ancillary;
State<Compressor> compressor;
Owner owner;
};
// lifetime extension on the X, no copies anywhere
// note that owner is last
auto&& [controller, ancillary, compressor, owner] = X();
// no lifetime extension, but also no copies because
// prvalue elision
auto [controller, ancillary, compressor, owner] = X();

inline auto makeOwner() {
struct bundle {
State<Controller> controller;
State<Ancillary> ancillary;
State<Compressor> compressor;
Owner owner = Owner(controller, ancillary, compressor);
bundle(bundle const&)=delete;
bundle& operator=(bundle const&)=delete;
};
return bundle{};
}
// using the function later
const auto&&[owner, controller, ancillary, compressor] = makeOwner();
here we use the fact that structs, even anonymous ones, can be unbundled like tuples.
Live example.

Related

Saving a const reference of a list that I get from another class is not working

My code is a bit more complicated, but I think the structure can be boiled down to this, imagine the following two classes:
class Foo {
std::list<std::shared_ptr<SomeType>> listOfType;
const std::list<std::shared_ptr<SomeType>>& getList() const {
return listOfType;
}
}
class A {
std::shared_ptr<Foo> foo;
Foo getFoo() {
return (*foo);
}
}
Now consider these three options, after the A class has filled the Foo's list:
A a;
// Do something with a, fills the list inside the foo shared pointer
// Option 1, save the copy of foo, then get the const reference of the list
Foo f = a.getFoo();
const std::list<std::shared_ptr<SomeType>>& fList = f.getList();
// Option 2, get a copy of the list directly from the returned copy of foo
std::list<std::shared_ptr<SomeType>> fList = a.getFoo().getList();
// Option 3, get the const list reference directly from the returned copy of foo
const std::list<std::shared_ptr<SomeType>>& fList = a.getFoo().getList();
Option 3 returns an empty list, the other two options return the list with the expected content.
The reason I'm creating this question is to know whether there is a problem that I don't see with this code (considering all the references and shared pointers, etc), otherwise it will be a problem with code, which would be beyond the scope of the question.
Thanks
Foo getFoo() {
return *foo;
}
In this member function, you are returning a temporary which is a prvalue in the calling expression. Because you call .getList() on it, it will get materialized and become a xvalue (expiring value) and as soon as the expression finishes, it will be destroyed and as you are capturing the reference of the returned list from a temporary object in the third 'option', it will become a dangling reference.
Foo & getFoo() {
return *foo;
}
However if you return a reference, it will be treated as a lvalue in the calling expression so the list won't be a dangling reference.

Extending the lifetime of a temporary object without copying it

Consider the following code:
#include <utility>
#include <iostream>
struct object {
object(const object&) = delete;
object(object&&) = delete;
object() {std::clog << "object::object()\n";}
~object() {std::clog << "object::~object()\n";}
void operator()() const {std::clog << "object::operator()()\n";}
};
struct wrapper {
const object& reference;
void operator()() const {reference();}
};
template <class Arg>
wrapper function(Arg&& arg) {
wrapper wrap{std::forward<Arg>(arg)};
return wrap;
}
int main(int argc, char* argv[]) {
wrapper wrap = function(object{}); // Let's call that temporary object x
wrap();
return 0;
}
I am really surprised that it prints:
object::object()
object::~object()
object::operator()()
Question 1: Why is the lifetime of object x not extended past the function call even if a const reference has been bound to it?
Question 2: Is there any way to implement the wrapper so that it would extend the lifetime of x past the function call?
Note: The copy and move constructors of the object have been explicitly deleted to make sure only one instance of it exists.
Why is the lifetime of object x not extended past the function call even if a const reference has been bound to it?
Technically, the lifetime of the object is extended past the function call. It is not however extended past the initialization of wrap. But that's a technicality.
Before we dive in, I'm going to impose a simplification: let's get rid of wrapper. Also, I'm removing the template part because it too is irrelevant:
const object &function(const object &arg)
{
return arg;
}
This changes precisely nothing about the validity of your code.
Given this statement:
const object &obj = function(object{}); // Let's call that temporary object x
What you want is for the compiler to recognize that "object x" and obj refer to the same object, and therefore the temporary's lifetime should be extended.
That's not possible. The compiler isn't guaranteed to have enough information to know that. Why? Because the compiler may only know this:
const object &function(const object &arg);
See, it's the definition of function that associates arg with the return value. If the compiler doesn't have the definition of function, then it cannot know that the object being passed in is the reference being returned. Without that knowledge, it cannot know to extend x's lifetime.
Now, you might say that if function's definition is provided, then the compiler can know. Well, there are complicated chains of logic that might prevent the compiler from knowing at compile time. You might do this:
const object *minimum(const object &lhs, const object &rhs)
{
return lhs < rhs ? lhs : rhs;
}
Well, that returns a reference to one of them, but which one will only be determined based on the runtime values of the object. Whose lifetime should be extended by the caller?
We also don't want the behavior of code to change based on whether the compiler only has a declaration or has a full definition. Either it's always OK to compile the code if it only has a declaration, or it's never OK to compile the code only with a declaration (as in the case of inline, constexpr, or template functions). A declaration may affect performance, but never behavior. And that's good.
Since the compiler may not have the information needed to recognize that a parameter const& lives beyond the lifetime of a function, and even if it has that information it may not be something that can be statically determined, the C++ standard does not permit an implementation to even try to solve the problem. Thus, every C++ user has to recognize that calling functions on temporaries if it returns a reference can cause problems. Even if the reference is hidden inside some other object.
What you want cannot be done. This is one of the reasons why you should not make an object non-moveable at all unless it is essential to its behavior or performance.
As far as I know, the only case the lifetime if extended if for the return value of a function,
struct A { int a; };
A f() { A a { 42 }; return a`}
{
const A &r = f(); // take a reference to a object returned by value
...
// life or r extended to the end of this scope
}
In your code, you pass the reference to the "constructor" of A class. Thus it is your responsability to ensure that the passed object live longer. Thus, your code above contains undefined behavior.
And what you see would probably be the most probable behavior in a class that do not make reference to any member. If you would access object member (including v-table), you would most likely observe a violation access instead.
Having said that, the correct code in your case would be:
int main(int argc, char* argv[])
{
object obj {};
wrapper wrap = function(obj);
wrap();
return 0;
}
Maybe what you want is to move the temporary object into the wrapper:
struct wrapper {
wrapper(object &&o) : obj(std::move(o)) {}
object obj;
void operator()() const {obj();}
};
In any case, the original code does not make much sense because it is build around false assumption and contains undefined behavior.
The life of a temporary object is essentially the end of the expression in which it was created. That is, when processing wrap = function(object{}) is completed.
So in resume:
Answer 1 Because you try to apply lifetime extension to a context other that the one specified in the standard.
Answer 2 As simple as moving the temporary object into a permanent one.

How do constant references work?

Recently I have been learning about good programming practice in C++ and found out that many programs pass objects to functions by reference so that multiple instances are not created. I have also learned that passing a constant reference prevents the original object from being modified however I do not understand how this works exactly. Shouldn't a constant reference create a new instance because the original object cannot be modified through the reference but the reference can still be used like a separate object? I'm fairly certain that this is not how it works but then, how does it work? Is there something I missed?
I have also learned that passing a constant reference prevents the original object from being modified [...]
Not quite. You are not allowed to modify the object through the const &. In other words, you have read-only access. But nothing natively prevents other code with read-write access (for example the original owner of the referred object) to modify it. You do need to be careful when designing so that such changes do not surprise you.
A constant reference (const&) is similar to a pointer to a constant object. You are allowed to read it through the reference but not modify it. Others, holding a non-const reference can still modify it.
Shouldn't a constant reference create a new instance because the
original object cannot be modified through the reference but the
reference can still be used like a separate object?
It's better to call it a reference to a constant object. This makes it much clearer how the thing works. Calling it the other way around is just confusing because any reference is constant (meaning you can't let it refer to another object after initialization).
So a reference to a constant object is just an additional name for an existing object (like a non-const reference) with the restriction that this name only allows reading from the existing object.
This means that through a reference to a constant object you can:
only read from member variables of the object, but not assign to them, unless a member is marked as mutable
only call methods of the object that are marked as const
Example:
struct Foo
{
int a;
mutable int b;
void SetA( int newA ) { a = newA; }
int GetA() const { return a; }
};
void DoSomething( const Foo& f )
{
// Here, f is just another name for foo, but it imposes some restrictions:
f.a = 42; // compiler error, can't modify member!
f.SetA( 42 ); // compiler error, can't call non-const method!
int x = f.a; // OK, reading is allowed.
f.b = 42; // OK, because b is marked as mutable
int y = f.GetA(); // OK, because GetA() is marked as const
}
int main()
{
Foo foo;
DoSomething( foo );
}

Creating an object, local variable vs rvalue reference

Is there any advantage to using an r value reference when you create an object, that would otherwise be in a normal local variable?
Foo&& cn = Foo();
cn.m_flag = 1;
bar.m_foo = std::move(cn);
//cn is not used again
Foo cn;
cn.m_flag = 1;
bar.m_foo = std::move(cn); //Is it ok to move a non rvalue reference?
//cn is not used again
In the first code snippet, it seems clear that there will not be any copies, but I'd guess that in the second the compile would optimize copies out?
Also in the first snippet, where is the object actually stored in memory (in the second it is stored in the stack frame of the enclosing function)?
Those code fragments are mostly equivalent. This:
Foo&& rf = Foo();
is binding a temporary to a reference, which extends the lifetime of the temporary to that of the reference. The Foo will only be destroyed when rf goes out of scope. Which is the same behavior you get with:
Foo f;
with the exception that in the latter example f is default-initialized but in the former example rf is value-initialized. For some types, the two are equivalent. For others, they are not. If you had instead written Foo f{}, then this difference goes away.
One remaining difference pertains to copy elision:
Foo give_a_foo_rv() {
Foo&& rf = Foo();
return rf;
}
Foo give_a_foo() {
Foo f{};
return f;
}
RVO is not allowed to be performed in the first example, because rf does not have the same type as the return type of give_a_foo_rv(). Moreover, rf won't even be automatically moved into the return type because it's not an object so it doesn't have automatic storage duration, so that's an extra copy (until C++20, in which it's an extra move):
Foo f = give_a_foo_rv(); // a copy happens here!
Foo g = give_a_foo(); // no move or copy
it seems clear that there will not be any copies
That depends entirely on what moving a Foo actually does. If Foo looks like:
struct Foo {
Foo() = default;
Foo(Foo const& ) = default;
Foo& operator=(Foo const& ) = default;
// some members
};
then moving a Foo still does copying.
And yes, it is perfectly okay to std::move(f) in the second example. You don't need an object of type rvalue reference to T to move from it. That would severely limit the usefulness of moving.

Is it good practice to bind shared pointers returned by functions to lvalue references to const?

Although it took me a while to get used to it, I now grew the habit of letting my functions take shared pointer parameters by lvalue-reference to const rather than by value (unless I need to modify the original arguments, of course, in which case I take them by lvalue-reference to non-const):
void foo(std::shared_ptr<widget> const& pWidget)
// ^^^^^^
{
// work with pWidget...
}
This has the advantage of avoiding an unnecessary copy of a shared pointer, which would mean thread-safely increasing the reference counting and potentially incurring in undesired overhead.
Now I've been wondering whether it is sane to adopt a somewhat symmetrical habit for retrieving shared pointers that are returned by value from functions, like at the end of the following code snippet:
struct X
{
// ...
std::shared_ptr<Widget> bar() const
{
// ...
return pWidget;
}
// ...
std::shared_ptr<Widget> pWidget;
};
// ...
// X x;
std::share_ptr<Widget> const& pWidget = x.bar();
// ^^^^^^
Are there any pitfalls with adopting such a coding habit? Is there any reason why I should prefer, in general, assigning a returned shared pointer to another shared pointer object rather than binding it to a reference?
This is just a remake of the old question of whether capturing a const reference to a temporary is more efficient than creating a copy. The simple answer is that it isn't. In the line:
// std::shared_ptr<Widget> bar();
std::shared_ptr<Widget> const & pWidget = bar();
The compiler needs to create a local unnamed variable (not temporary), initailize that with the call to bar() and then bind the reference to it:
std::shared_ptr<Widget> __tmp = bar();
std::shared_ptr<Widget> const & pWidget = __tmp;
In most cases it will avoid the creation of the reference and just alias the original object in the rest of the function, but at the end of the day whether the variable is called pWidget or __tmp and aliased won't give any advantage.
On the contrary, for the casual reader, it might look like bar() does not create an object but yield a reference to an already existing std::shared_ptr<Widget>, so the maintainer will have to seek out where bar() is defined to understand whether pWidget can be changed outside of the scope of this function.
Lifetime extension through binding to a const reference is a weird feature in the language that has very little practical use (namely when the reference is of a base and you don't quite care what the exact derived type returned by value is, i.e. ScopedGuard).
You may have the optimization backwards:
struct X
{
// ...
std::shared_ptr<Widget> const& bar() const
{
// ...
return pWidget;
}
// ...
std::shared_ptr<Widget> pWidget;
};
// ...
// X x;
std::share_ptr<Widget> pWidget = x.bar();
As bar is returning a member variable, it must take a copy of the shared_ptr in your version. If you return the member variable by reference the copy can be avoided.
This doesn't matter in both your original version and the version shown above, but would come up if you called:
x.bar()->baz()
In your version a new shared_ptr would be created, and then baz would be called.
In my version baz is called directly on the member copy of the shared_ptr, and the atomic reference increment/decrement is avoided.
Of course the cost of the shared_ptr copy constructor (atomic increment) is very small, and not even noticable in all but the most performance-sensetive applications. If you are writing a performance sensetive application than the better option would be to manage memory manually with a memory pool architecture and then to (carefully) use raw pointers instead.
Adding on top of what David Rodríguez - dribeas said namely, binding to a const reference doesn't save you from making the copy and the counter is incremented anyway, the following code illustrates this point:
#include <memory>
#include <cassert>
struct X {
std::shared_ptr<int> p;
X() : p{new int} {}
std::shared_ptr<int> bar() { return p; }
};
int main() {
X x;
assert(x.p.use_count() == 1);
std::shared_ptr<int> const & p = x.bar();
assert(x.p.use_count() == 2);
return 0;
}