How can a class template store either reference or value? - c++

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.

Related

Meaning of std::forward<std::decay_t<F>>(f)

I have a question about the code written in https://koturn.hatenablog.com/entry/2018/06/10/060000
When I pass a left value reference, if I do not remove the reference with std::decay_t, I get an error.
Here is the error message
'error: 'operator()' is not a member of 'main()::<lambda(auto:11, int)>&
I don't understand why it is necessary to exclude the left value reference.
I would like to know what this error means.
#include <iostream>
#include <utility>
template <typename F>
class
FixPoint : private F
{
public:
explicit constexpr FixPoint(F&& f) noexcept
: F{std::forward<F>(f)}
{}
template <typename... Args>
constexpr decltype(auto)
operator()(Args&&... args) const
{
return F::operator()(*this, std::forward<Args>(args)...);
}
}; // class FixPoint
namespace
{
template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}
} // namespace
int
main()
{
auto body = [](auto f, int n) -> int {
return n < 2 ? n : (f(n - 1) + f(n - 2));
};
auto result = makeFixPoint(body)(10);
std::cout << result << std::endl;
}
I don't understand why it is necessary to exclude the left value
reference. I would like to know what this error means.
When you pass an lvalue lambda into makeFixPoint(), the template parameter F of makeFixPoint() is instantiated as L&, where L is the lambda type. In the function body, FixPoint<std::decay_t<F>>{...} will be instantiated as FixPoint<L>{...}, so the constructor of FixPoint is instantiated as
explicit constexpr FixPoint(L&& f);
which accepts a lambda type of rvalue reference. In makeFixPoint(), if you initialize it with {std::forward<F>(f)} i.e. {std::forward<L&>(f)}, f will be forwarded as an lvalue, which will be ill-formed since rvalue reference cannot bind an lvalue.
The purpose of using {std::forward<std::decay_t<F>>(f)} is to force f to be forwarded as an rvalue.
,The code you have shared is toxic.
template <typename F>
class FixPoint : private F
{
public:
explicit constexpr FixPoint(F&& f)
what this means is that we expect F to be a value type (because inheriting from a reference isn't possible). In addition, we will only accept rvalues -- we will only move from another F.
: F{std::forward<F>(f)}
{}
this std::forward<F> is pointless; this indicates that the person writing this code thinks they are perfect forwarding: they are not. The only legal types F are value types (not references), and if F is a value type F&& is always an rvalue reference, and thus std::forward<F> is always equivalent to std::move.
There are cases where you want to
template<class X>
struct wrap1 {
X&& x;
wrap1(X&& xin):x(std::forward<X>(xin)){}
};
or even
template<class X>
struct wrap2 {
X x;
wrap2(X&& xin):x(std::forward<X>(xin)){}
};
so the above code is similar to some use cases, but it isn't one of those use cases. (The difference here is that X or X&& is the type of a member, not a base class; base classes cannot be references).
The use for wrap2 is when you want to "lifetime extend" rvalues, and simply take references to lvalues. The use for wrap1 is when you want to continue the perfect forwarding of some expression (wrap1 style objects are generally unsafe to keep around for more than a single line of code; wrap2 are safe so long as they don't outlive any lvalue passed to them).
template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}
Ok more red flags here. inline constexpr is a sign of nonsense; constexpr functions are always inline. There could be some compilers who treat the extra inline as meaning something, so not a guarantee of a problem.
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
std::forward is a conditional move. It moves if there is an rvalue or value type passed to it. decay is guaranteed to return a value type. So we just threw out the conditional part of the operation, and made it into a raw move.
return FixPoint<std::decay_t<F>>{std::move(f)};
this is a simpler one that has the same meaning.
At this point you'll probably see the danger:
template <typename F>
constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::move(f)};
}
we are unconditionally moving from the makeFixPoint argument. There is nothing about the name makeFixPoint that says "I move from the argument", and it accept forwarding references; so it will silently consume an lvalue and move-from it.
This is very rude.
So a sensible version of the code is:
template <typename F>
class FixPoint : private F
{
public:
template<class U,
class dU = std::decay_t<U>,
std::enable_if_t<!std::is_same_v<dU, FixPoint> && std::is_same_v<dU, F>, bool> = true
>
explicit constexpr FixPoint(U&& u) noexcept
: F{std::forward<U>(u)}
{}
[SNIP]
namespace
{
template <typename F>
constexpr FixPoint<std::decay_t<F>>
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<F>(f)};
}
} // namespace
that cleans things up a bit.
The template just can't work as intended this way.
First, because it is supposed to be possible to inherit from F, it doesn't make much sense to use std::decay_t. Instead it is probably supposed to be just std::remove_cvref_t, but before C++20 that was kind of cumbersome to write and std::decay_t does almost the same.
In the class template F is intended to be a non-reference type. It is not a template parameter of the constructor. So it cannot be used for perfect-forwarding. The constructor argument will always be a rvalue reference which cannot take lvalues.
Even if a lvalue is passed to makeFixPoint, the call std::forward<std::decay_t<F>>(f) forces conversion to a rvalue reference, which then can be used in the constructor. That is clearly dangerous. The function always behaves as if you had std::moved the argument into it.
It should just be std::forward<F>(f) and the constructor should take another template parameter that can be used to form a forwarding reference:
template<typename G>
requires std::is_same_v<std::remove_cvref_t<G>, F>
explicit constexpr FixPoint(G&& g) noexcept
: F{std::forward<G>(g)}
{}

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 I construct std::tuple of references from a parameter pack?

I have a builder class that I'd like to store arguments as references for use in subsequent building.
I'd like to pass in a variable number of arguments to my class, infer the template arguments using class template argument deduction, and store those passed arguments as references in a std::tuple.
What's the easiest way to convert from a parameter pack to a std::tuple of references?
I found std::forward_as_tuple which does something similar to what I want, but I don't want a forwarding reference plus it gives a syntax error during initialization of the member tuple.
template <typename... Ts>
struct Builder
{
using ArgsT = decltype(std::forward_as_tuple(Ts{}...));
ArgsT args_;
Builder(Ts&... ts) : args_(ts...) {}
};
int main()
{
struct Foo
{
int a;
double b;
};
Foo foo{};
Builder fooBuilder{foo.a, foo.b};
}
The syntax error is:
error: no matching function for call to std::tuple<int&&, double&&>::tuple(int&, double&)
If you simply wants reference, use them directly:
template <typename... Ts>
struct Builder
{
using ArgsT = std::tuple<Ts&...>;
ArgsT args_;
Builder(Ts&... ts) : args_(ts...) {}
};
int main()
{
struct Foo
{
int a;
double b;
};
Foo foo{};
Builder fooBuilder{foo.a, foo.b};
}
For your code:
decltype(std::forward_as_tuple(Ts{}...)) resolves in std::tuple<Ts&&...>.
Ts{} creates a temporary (and requires your type to be default constructible).
And you cannot bind int& to int&&.
You might use decltype(std::forward_as_tuple(std::declval<Ts&>()...)) which resolves in std::tuple<Ts&...>, but later is simpler and provided solution;-).
Another way:
#include <tuple>
template<typename Tuple>
struct Builder
{
Tuple args_;
Builder(Tuple const& t) : args_(t) {}
};
int main()
{
struct Foo
{
int a;
double b;
};
Foo foo{};
Builder fooBuilder{std::tie(foo.a, foo.b)};
}
Builder(Ts&... ts) is a pack of lvalue references.
Ts{}... is a pack of prvalues of the same type.
std::forward_as_tuple(Ts{}...) is a tuple containing rvalue references to the same type.
lvalue references and rvalue references are not the same thing; you cannot assign one to the other. So args_(ts...) generates the appropriate error message.
There is more than one way to produce a solution to what could be your problem.
template <typename... Ts>
struct Builder
{
using ArgsT = std::tuple<Ts&&...>; // (1)
using ArgsT = std::tuple<Ts&...>; // (2)
using ArgsT = std::tuple<Ts...>; // (3)
all 3 of these are actually reasonable ways to solve your problem, depending on later code options. Pick one depending on what your real problem is.
ArgsT args_;
What goes here:
Builder(Ts&&... ts) : args_(std::forward<Ts>(ts)...) {}
Builder(Ts&... ts) : args_(ts...) {}
Builder(Ts&&... ts) : args_(std::forward<Ts>(ts)...) {}
for each of the above 3 cases.
In case (1) you perfect forward your arguments into a tuple, and the tuple stores rvalue references to any rvalue arguments. In case (2) you only take lvalue arguments, and you store a tuple of lvalue references to them. In case (3) you perfect forward your arguments into a tuple, and store values if the argument was an rvalue and lvalue references it he value was an lvalue.
(3) is useful when you want your references to outlive the current line; the only safe way to do that is to store a copy, and then move into it.
(2) is useful if you only want to refer to lvalues.
(1) is useful if your Builder object is not going to outlive the current line, and you don't want to pay for even moving objects. It is a tad more dangerous than (1).
All 3 will make your sample code compile.

Do I use std::forward or std::move here?

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.

c++ rvalue reference and const qualifier

Among the many benefits of const qualification is to make an API more understandable, example:
template<typename T> int function1(T const& in);
// clearly, the input won’t change through function1
With the introduction of rvalue references, one can benefit from perfect forwarding but often const qualifiers are removed, example:
template<typename T> int function2(T&& in);
// can explicitly forward the input if it's an rvalue
Apart from documentation, is there a good way to describe that function2 won’t change its input?
template<typename T> int function2(T&& in);
// can explicitly forward the input if it's an rvalue
Apart from documentation, is there a good way to describe that
function2 won’t change its input?
Yes. Stick with the C++03 solution:
template<typename T> int function1(T const& in);
// clearly, the input won’t change through function1
The benefits of perfect forwarding are that you don't want to assume if something is const or non-const, lvalue or rvalue. If you want to enforce that something is not modified (i.e. that it is const), then explicitly say so by adding const.
You could do this:
template<typename T> int function1(T const&& in);
// clearly, the input won’t change through function1
However everyone who read your code would wonder why you've used rvalue references. And function1 would cease to accept lvalues. Just use const & instead and everyone will understand. It is a simple and well understood idiom.
You don't want to perfectly forward. You want to enforce immutability.
You could say this:
template <typename T>
typename std::enable_if<immutable<T>::value, int>::type
function(T && in)
{
// ...
}
where you have something like:
template <typename T> struct immutable
: std::integral_constant<bool, !std::is_reference<T>::value> {};
template <typename U> struct immutable<U const &>
: std::true_type {};
This way, the template will only be usable if the universal reference is either a const-reference (so T = U const &) or an rvalue-reference (so T is not a reference).
That said, if the argument is not going to be changed, you could just use T const & and be done with it, since there's nothing to be gained from binding mutably to temporary values.