Reading about universal references led me to wonder: how can I construct a class template such that it stores by reference if possible, or by value if it must?
That is, can I do something like this
template <class T> class holder { T obj_m; // should be a reference if possible... public: holder(T t) :obj_m { t } {} } auto hold_this(T && t) { return holder<T>(t); }
Except that when hold_this()
is given an lvalue the holder will hold a reference, and when given an rvalue the holder will make a copy?
Definition. As per the standard definition, a template class in C++ is a class that allows the programmer to operate with generic data types. This allows the class to be used on many different data types as per the requirements without the need of being re-written for each type.
A class template is a template that is used to generate classes whereas a template class is a class that is produced by a template.
The relationship between a class template and an individual class is like the relationship between a class and an individual object. An individual class defines how a group of objects can be constructed, while a class template defines how a group of classes can be generated.
Deriving from a non-template base classIt is quite possible to have a template class inherit from a 'normal' class. This mechanism is recommended if your template class has a lot of non-template attributes and operations. Instead of putting them in the template class, put them into a non-template base class.
Except that when hold_this() is given an lvalue the holder will hold a reference, and when given an rvalue the holder will make a copy?
You already wrote it (minus the required template <typename T>
). The deduction rules for a forwarding reference preserve value category as follows:
t
is bound to an lvalue of type T2
, then T = T2&
.t
is bound to an rvalue of type T2
, then T = T2
.It's those deduction rules that std::forward
relies on to do its job. And why we need to pass the type to it as well.
The above means that you instantiate holder
directly with T2
in the rvalue case. Giving you exactly what you want. A copy is made.
As a matter of fact, two copies are made. Once to create the constructor argument t
, and the other copy is to initialize obj_m
from it. But we can get rid of it with some clever use of type_traits:
template <class T> class holder { T obj_m; // should be a reference if possible... public: holder(std::add_rvalue_reference_t<T> t) :obj_m { std::forward<T>(t) } {} }; template<typename T> auto hold_this(T && t) { return holder<T>(std::forward<T>(t)); }
See it live. We use add_rvalue_reference_t
to make t
be of the correct reference type in each case. And "simulate" the argument deduction which would make obj_m { std::forward<T>(t) }
resolve to initializing obj_m
from the correct reference type.
I say "simulate" because it's important to understand the constructor argument for holder
cannot be a forwarding reference because the constructor itself is not templated.
By the way, since you tagged c++17, we can also add a deduction guide to your example. If we define it as follows (with the feedback from T.C. incorporated):
template <class T> class holder { T obj_m; // should be a reference if possible... public: holder(T&& t) :obj_m { std::forward<T>(t) } {} }; template<typename T> holder(T&&) -> holder<T>;
Then this live example shows you can define variables as hold h1{t};
and hold h2{test()};
, with the same deduced types as the function return values from before.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With