Do I use std::forward or std::move here? - c++

Let's say I have:
template<class T>
struct NodeBase
{
T value;
NodeBase(T &&value)
: value(value) { }
};
and I inherit from it:
template<class T>
struct Node : public NodeBase<T>
{
Node(T &&value)
: NodeBase( WHAT_GOES_HERE (value)) { }
};
Should WHAT_GOES_HERE be std::move or std::forward<T>? Why?

Since in the implementation of the constructor of Node<T> it is unknown whether T is a plain type (i.e. not a reference), or a reference,
std::forward<T>(value)
is suitable.
std::forward<T>(value) is the right choice whenever it isn't known whether T && binds to an rvalue or an lvalue. This is the case here because in the constructor we don't know whether T && is equivalent to U && for some plain type U, or equivalent to U & &&.
It doesn't matter whether T is deduced in the function call that uses std::forward or determined at a different time (such as in your example, where T is determined at the time when the Node template is instantiated).
std::forward<T>(value) will call the inherited constructor in the same way as if the base class constructor had been called directly by the caller. I.e., it will call it on an lvalue when value is an lvalue, and on an rvalue when value is an rvalue.

Probably neither.
What I suspect you should have is:
template<class T>
struct NodeBase
{
T value;
NodeBase(NodeBase &&) = default;
NodeBase(NodeBase const&) = default; // issue: this might not work with a `T&`, but we can conditionally exclude it through some more fancy footwork
NodeBase(NodeBase &) = default;
template<typename U, typename=typename std:enable_if< std::is_convertible<U&&, T>::value >::type >
NodeBase(U &&u)
: value(std::forward<U>(u)) { }
};
template<class T>
struct Node : public NodeBase<T>
{
Node( Node & ) = default;
Node( Node const& ) = default; // issue: this might not work with a `T&`, but we can conditionally exclude it through some more fancy footwork
Node( Node && ) = default;
template<typename U, typename=typename std:enable_if< std::is_convertible<U&&, NodeBase<T>>::value >::type>
Node(U && u)
: NodeBase( std::forward<U>(u) ) { }
};
unless you are doing something exceedingly strange.
By exceedingly strange, it means that if your T is an int, you want to only accept moved-from values into your Node, but if your T is an int&, you accept only non-const lvalue ints, and if T is an int const&, you accept any value convertible to int.
This would be a strange set of requirements to place on the constructor of NodeBase. I can think of situations where this might be the case, but they are not common.
Assuming you simply want NodeBase to store that data, taking T&& in the constructor is not the right thing to do -- if you are storing an int in NodeBase, you probably are willing to make copies of that int, instead of only accepting moved-from ints.
The above code does exactly that -- it allows anything that could be stored in the NodeBase to be passed on up to said NodeBase, with perfect forwarding.
On the other hand, if you actually want the strange set of construction restrictions, then this is not the right answer for you. I've used that when I was building the a template type that was built from a universal reference argument, and I did want to restrict the passed in type to match the universal reference argument exactly, and store it iff the argument was an rvalue reference, and otherwise keep a reference to it.

T isn't deduced in your example. The T is a class template parameter, not a function template parameter. So assuming you will not use a reference type as T, T&& will be an rvalue-reference to T, so it will only bind to rvalues of type T. these will be safe to move so you can use std::move here.
template<class T>
struct Node : public NodeBase<T>
{
Node(T &&value)
: NodeBase( std::move (value)) { }
};
int main()
{
int i = 3;
Node<int> x(42); // ok
Node<int> y(std::move(i)); // ok
Node<int> z(i); // error
}
std::forward is normally only for places where you have a deduced type that may either be an lvalue-reference or rvalue-reference.
template<class T>
void f(T&& x)
{
... std::forward<T>(x) ...
}
Because T&& may actually be either an lvalue-reference or rvalue-reference. This is only because T is deduced in this context.

Related

Pass object with derived template to function that accepts object with base template

How do I pass an object with a derived template instantiation to a method accepting those objects with a base template instantiation?
It seems possible, as std::shared_ptr or std::pair seem capable to do it.
E.g.
#pragma once
#include <iostream>
#include <memory>
struct Base {
virtual void print() = 0;
};
struct Derived : public Base {
void print() {
std::cout << "Got it!" << std::endl;
}
};
void printBase(const std::shared_ptr<Base> &ptr){
ptr->print();
}
void printBase(const std::pair<Base&, Base&> &pr){
pr.first.print();
}
template <typename T>
struct Wrap {
T& t;
};
void printBase(const Wrap<Base> &wrap) {
wrap.t.print();
}
int main() {
Derived d;
std::shared_ptr<Derived> ptr = std::make_shared<Derived>(d);
printBase(ptr); // works
std::pair<Derived&, Derived&> pr = {d, d};
printBase(pr); // works
Wrap<Derived> w = Wrap<Derived>{d};
// printBase(w); // gives compile error
}
You will need to explicitly add a conversion constructor and/or assignment operator to your Wrapped type to be able to convert from different types.
This is how both std::shared_ptr and std::pair do this internally; shared_ptr<T> can be constructed from shared_ptr<U> types (with SFINAE restrictions that U* is convertible to T*), and pair<T,U> can be constructed from pair<T2,U2> types (with SFINAE restrictions that T2 is convertible to T and U2 is convertible to U).
Doing this can be as simple as adding a new constructor:
template <typename T>
struct Wrap
{
Wrap(T& ref)
: t{ref}
{
}
template <typename U, typename = std::enable_if_t<std::is_convertible_v<U&, T&>>>
Wrap(const Wrap<U>& other)
: t{other.t}
{
}
T& t;
};
The above example uses is_convertible as a condition for enable_if so the constructor is only visible when references of U can be converted to references of T. This will constrain it such that U must be hierarchically related to T (since references aren't convertible otherwise) -- which would allow a Wrapped<Derived> to be converted to Wrapped<Base>, but not vice-versa.
Edit: As mentioned in the comments, it's worth noting that, unlike types that are part of a hierarchy -- where a reference to Derived can be passed as a reference to Base, types that wrap hierarchies will not be able to pass a reference to a Template<Derived> as a reference to a Template<Base>.
The examples with a std::shared_ptr<Derived> being passed to a const std::shared_ptr<Base>& only actually work due to const-lifetime extension in C++. This doesn't actually pass it as a reference -- but instead materializes a temporary object of std::shared_ptr<Base> which gets passed to the reference. It's effectively the same as passing-by-value.
This also means that you cannot have a Template<Derived> be passed to a non-const Template<Base> reference, since lifetime extension only occurs for const references.
Edit: As discussed in the comments:
Making the constructor a copy conversion is not required; it could easily be an R-value constructor instead. However, if you are doing cleanup on destruction of the wrapped type, then you will need to somehow flag that the moved-from object does not need to be cleaned up. The easiest way is using pointers, where you rebind to nullptr after moving:
template <typename T>
struct Wrap
{
Wrap(T& ref)
: t{std::addressof(ref)}
{
}
Wrap(Wrap&& other)
: t{other.t}
{
other.t = nullptr;
}
Wrap(const Wrap&) = delete;
template <typename U, typename = std::enable_if_t<std::is_convertible_v<U&, T&>>>
Wrap(Wrap<U>&& other)
: t{other.t}
{
other.t = nullptr;
}
~Wrap() {
if (t != nullptr) {
cleanup(*t); // some cleanup code
}
}
T* t;
};
If pointers aren't possible for your desired API, then you may need to use a bool needs_cleanup that gets set appropriately during moves, since references cannot be rebound.
Note: if the data is private and not public as in this example, you may need to have a friend declaration of:
template <typename> friend class Wrap;
So that a Wrap<T> may access the private data members of a Wrap<U>.
Change printBase function to the following one:
template <typename T, typename = std::enable_if<std::is_base_of_v<Base, T>>>
void printBase(const Wrap<T> &wrap){
wrap.t.print();
}

How can a class template store either reference or value?

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?
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:
If t is bound to an lvalue of type T2, then T = T2&.
If 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.

When does && mean 'forwarding reference'?

When compiled, the following code causes this error:
'Container::Wrapper::Wrapper(S)': member function already
defined or declared
Does the compiler think S&& in the constructor of Wrapper is a forwarding reference?
template<typename T>
struct Container
{
template<class S>
struct Wrapper {
S obj;
Wrapper(S&& obj) : obj(std::forward<S>(obj)){}
Wrapper(const S& obj) : obj(obj){}
};
template<class S>
void push_back(S&& obj) {
void *storage = malloc(sizeof(Wrapper<S>));
new (storage) Wrapper<S>(std::forward<S>(obj));
}
};
struct Foobar{};
int main()
{
Container<Foobar> cont;
Foobar foobar;
cont.push_back(foobar);
return 0;
}
Looking at an example from here, I don't understand how what I'm doing is any different:
template <class T, class Allocator = allocator<T> >
class vector {
public:
...
void push_back(T&& x); // fully specified parameter type ⇒ no type deduction;
... // && ≡ rvalue reference
};
Edit:
The solution to this issue was to modify push_back to remove the reference from the type being used to instantiate the Wrapper:
template<class S>
void push_back(S&& obj) {
typedef std::remove_reference<S>::type U;
void *storage = malloc(sizeof(Wrapper<U>));
new (storage) Wrapper<U>(std::forward<S>(obj));
}
There are no universal references involved it the implementation of Wrapper. The only universal reference in your code is Container<>::push_back's parameter.
When you call cont.push_back(foobar);, parameter S of push_back is deduced as Foobar &.
Later you attempt to instantiate your Wrapper<S> with S == Foobar &. Reference collapsing rules dictate that in Wrapper's constructor parameter declarations S && turns into Foobar & and const S & also turns into Foobar &.
This means that you end up with two supposedly "overloaded" Wrapper::Wrapper constructors, which have identical signatures. This is the reason for the error message you observe.
If you attempt to instantiate std::vector with a template argument of lvalue reference type, you will run into exactly the same problem for its push_back overloads. However, with such an attempt compilation will typically fail miserably for other reasons, well before it gets to push_back overloads.
A more distilled example would look as follows
template <typename T> struct MyVector {
void foo(T &&) {}
void foo(const T &) {}
};
int main() {
MyVector<int &> v;
}
and will produce the same error.
The fairly non-obvious part here is that const T & with T = U & actually becomes U & and not const U &. But that's indeed the case.

Type of member variable should depend on constructor argument's type

I try to define a class A as follows:
template< typename T >
class A
{
public:
A( T elem )
: _elem( elem )
{}
private:
TYPE _elem; // "TYPE" should be either "T" in case "elem" is an r-value or "T&" in case "elem" is an l-value.
};
Here, I want _elem to have either the type T in case that the constructor's argument elem is an r-value or the type T& in case elem is an l-value.
Does anyone know how this can be implemented?
Until we get template argument deduction for class templates, you'll need to use a helper function for this:
template <typename T>
auto make_a (T&& elem) {
return A<T>{std::forward<T>(elem)};
}
This uses a forwarding reference to deduce whether the argument is an lvalue or rvalue and constructs the A by perfectly forwarding the argument. Taking int as an example, if an lvalue is passed, T will be int&, and if an rvalue is passed, T will be int.
Your A template should just look like this:
template< typename T >
class A
{
public:
A( T elem )
: _elem( elem )
{}
private:
T _elem;
};
You could make make_a a friend and make the constructor private if you only want to allow construction from the factory method.

Constructor design for a class which delegates a value to one of its member variables

Please consider the following tree class
template<typename T>
class tree
{
public:
template<typename U>
tree(U&& value)
: m_value(std::forward<U>(value))
{ }
private:
T m_value;
};
and some verbose class foo which is constructed by its std::string name which is printed via std::cout whenever one of its constructors is invoked.
foo a("a"), b("b");
foo const c("c");
tree<foo> s(std::move(a)), t(b), u(c);
yields the following output:
a constructed
b constructed
c constructed
a moved
b copied
c copied
which is as expected.
How does this work?
To be specific: We cannot use
tree(T&& value)
: m_value(std::forward<T>(value))
{ }
Why? Well, cause we force decltype(value) to be foo&& which is not the case in t(b) and u(c). We would need to provide
tree(T const& value)
: m_value(value)
{ }
as well, if we want to use this arguable variant. The ctor used in the tree implementation works cause of the reference collapsing rules.
But why can't we use
template<typename U>
tree(U&& value)
: m_value(std::forward<T>(value))
{ }
instead?
Well, the reason is that in u(c) we have U = foo const& = decltype(value), but std::forward<T> only accepts foo& or foo&&, since T = foo. In contrast, std::forward<U> accepts foo const& (or foo const&&), since U = foo const&.
So, if I'm not overlooking something, we should use template<typename U> tree(U&&) instead of a tree(T const&), tree(T&&) combination. But: The compiler will take the template<typename U> tree(U&&) ctor in push_back, where the intend is that the move ctor of tree is taken:
foo a("a"), b("b");
foo const c("c");
tree<foo> t("root");
t.emplace_back(std::move(a));
t.emplace_back(b);
t.emplace_back(c);
tree<foo> u("other root");
u.push_back(t); // cannot convert argument 1 from 'tree<foo>' to 'std::string const&'
What can I do? Do I need to use the tree(T const&), tree(T&&) combination instead?
[Sorry for being a bit too verbose, but I felt responsible for clarifying any technical issue before asking for design.]
A greedy constructor such as template<typename U> tree(U&&) must be suitably constrained, or you'll have a lot of problems down the road. You saw it hijack copy construction from a non-const lvalue because it's a better match than the copy constructor. It also defines an implicit conversion from everything under the sun, which can have "fun" effects on overload resolution.
A possible constraint might be "accept only things that are convertible to T":
template<typename U, class = std::enable_if_t<std::is_convertible_v<U, T>>>
tree(U&& u) : m_value(std::forward<U>(u)) {}
Or perhaps "accept only things that are convertible to T and not a tree".
template<class U>
using not_me = std::negation<std::is_same<U, tree>>;
template<typename U,
class = std::enable_if_t<std::conjunction_v<not_me<std::decay_t<U>>,
std::is_convertible<U, T>>>>
tree(U&& u) : m_value(std::forward<U>(u)) {}
You can do both.
If you go with:
template<typename U>
tree(U&& value)
: m_value(std::forward<U>(value))
{ }
the downside is that any one-argument constructor of T can be invoked this way. This may not be what you want. For example, with that variant, the following is valid:
struct foo
{
foo() = default;
foo(const foo &ref) = default;
explicit foo(int);
};
tree<foo> t(10);
t = 20;
This is a decision you need to find for yourself, however, I personally see that as a huge downside. I would make that constructor explicit (eliminating the second initialisation) and go for tree(const T&) along with tree(T&&) to avoid the first initialisation.