patterncppMinor
Universal reference in a class template
Viewed 0 times
universalreferenceclasstemplate
Problem
I'm writing a templatized container and wanted to use an universal reference for the
This means that I cannot pass lvalues to the push_back function above.
In the case of std::vector this is solved by having a
However, since the code in my
However, if I am to templatize my add function, it does become universal reference. So I came up with the following code.
I've added the static assert to make sure that people do not actually templatize the
Here is a small ideone version for you to play around with.
Add(T&& object) function. Check out isocpp if you need an update on your universal/rvalue references. A quick example from that page states that, since the class is templatized, T&& is fully specified and therefore a rvalue, not an universal reference.template >
class vector {
public:
...
void push_back(T&& x); // fully specified parameter type ⇒ no type deduction;
... // && ≡ rvalue reference
};This means that I cannot pass lvalues to the push_back function above.
vector list;
list.Add(T()); // rvalue, compiles fine.
T data;
list.Add(data); // lvalue, does not compile.In the case of std::vector this is solved by having a
push_back(const T& object) overload.However, since the code in my
Add function is quite complex, I didn't want to copy it over. However, if I am to templatize my add function, it does become universal reference. So I came up with the following code.
#include
#include
#include
using namespace std;
template
class Container
{
public:
template
void Add(UR&& object)
{
static_assert(std::is_same>, T>::value, "UR and T should be the same!");
m_Vector.push_back(std::forward(object));
}
std::vector m_Vector;
};
class Data
{
public:
int x;
int y;
};
int main() {
Container list;
Data d;
list.Add(d);
return 0;
}I've added the static assert to make sure that people do not actually templatize the
Add function. Does anyone know a cleaner solution, since this feels a bit of template magic? Should I follow the std method and just copy my add function (which is a bit more complex than this minimal example)? Can anyone come up with a case where my solution will not work?Here is a small ideone version for you to play around with.
Solution
Your solution is principally correct. If you want to play nice with potential generic traits (at the cost of a potentially uglier error message), you'd replace the
This way, the function
If you don't like the "template magic" involved in this and/or the compiler errors generated from misuse, you can solve this by providing the two strongly-typed overloads as
This way, you can keep the
Note that the
Unrelated to the question at hand, but I assume
1 Note that "forwarding reference" is the preferred term instead of "universal reference," since it captures the semantics better.
static_assert with SFINAE:template , T>::value>
>
void Add(UR&& object)
{
m_Vector.push_back(std::forward(object));
}This way, the function
Add will not exist at all when UR is not a forwarding reference for T1.If you don't like the "template magic" involved in this and/or the compiler errors generated from misuse, you can solve this by providing the two strongly-typed overloads as
std does and forward to a common private implementation:void Add(const T& object)
{
AddImpl(object);
}
void Add(T&& object)
{
AddImpl(std::move(object));
}
private:
template
void AddImpl(UR&& object)
{
static_assert(std::is_same>, T>::value, "UR and T should be the same!");
m_Vector.push_back(std::forward(object));
}This way, you can keep the
static_assert-using version and keep the static assertion just to check your code's correctness, not the client code's.Note that the
static_assert needs to do std::remove_cv after std::remove_reference, otherwise it will fail for UR = const T&. The same applies to the original code in the question too.Unrelated to the question at hand, but I assume
using namespace std; is for this reduced example only and is not present in your real code. It's very bad practice to put that into header files in real code.1 Note that "forwarding reference" is the preferred term instead of "universal reference," since it captures the semantics better.
Code Snippets
template <
class UR = T,
class TypeMustBeT = std::enable_if_t<std::is_same<std::remove_reference_t<UR>, T>::value>
>
void Add(UR&& object)
{
m_Vector.push_back(std::forward<UR>(object));
}void Add(const T& object)
{
AddImpl(object);
}
void Add(T&& object)
{
AddImpl(std::move(object));
}
private:
template <class UR>
void AddImpl(UR&& object)
{
static_assert(std::is_same<std::remove_cv_t<std::remove_reference_t<UR>>, T>::value, "UR and T should be the same!");
m_Vector.push_back(std::forward<UR>(object));
}Context
StackExchange Code Review Q#155385, answer score: 7
Revisions (0)
No revisions yet.