I saw possible implementations for std::remove_reference as below
template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
Why is it that there are specializations for lvalue and rvalue reference? Won't the general template itself be sufficient and remove the reference? I'm confused here because in the T& or T&& specialization if I try to use ::type I should still get T& or T&& respectively right?
Could you explain how, why we cast to remove_reference<t>::type&& in move? (is it because that the parameter is named so it will be treated as an lvalue inside the move function?).
Also, could you point out a way whereby I can find out and print what the type is? for e.g if its an rvalue of type int then I should be able to print out that int&& was passed? (I've been using std::is_same to check but manually.)
Thank you for your time.
why is it that there are specializations for lvalue and rvalue reference?
If only the primary template existed, then doing:
remove_reference<int&>::type
Would give you:
int&
And doing:
remove_reference<int&&>::type
Would give you:
int&&
Which is not what you want. The specializations for lvalue references and rvalue references allow stripping the & and the &&, respectively, from the type argument you pass.
For instance, if you are doing:
remove_reference<int&&>
The type int&& will match the pattern specified by the T&& specialization, with T being int. Since the specialization defines the type alias type to be T (in this case, int), doing:
remove_reference<int&&>::type
Will give you int.
could you explain how, why we cast to remove_reference<t>::type&& in move?
That's because if move() were defined as follows:
template<typename T>
T&& move(T&& t) { ... }
// ^^^
// Resolves to X& if T is X& (which is the case if the input has type X
// and is an lvalue)
Then the return type will be X& if the argument of move() is an lvalue of type X (that's how so-called "universal references"). We want to make sure that the return type is always an rvalue reference.
The purpose of move() is to give you back an rvalue, no matter what you pass in input. Since a function call for a function whose return type is an rvalue reference is an rvalue, we really want move() to always return an rvalue reference.
That's why we do remove_reference<T>::type&&, because appending && to a non-reference type is always guaranteed to yield an rvalue reference type.
Also could you point out a way whereby I can find out and print what the type is?
I'm not sure what you mean by "print" here. There is no portable way I know of converting the name of a type to a string (no matter how you obtain that type).
If your goal is to make sure that an rvalue was passed, on the other hand, you could use a static assertion like so:
#include <type_traits>
template<typename T>
void foo(T&&)
{
static_assert(!std::is_reference<T>::value, "Error: lvalue was passed!");
// ...
}
Which relies on the fact that when an lvalue of type X is being passed, T will be deduced to be X&.
You could also use an equivalent SFINAE-constraint, if you only want to produce a substitution failure:
#include <type_traits>
template<typename T, typename std::enable_if<
!std::is_reference<T>::value>::type* = nullptr>
void foo(T&&)
{
// ...
}
When you treat some type as template parameter, compiler searches for the most "specialized" specialization. If you pass a int&& to this template, compiler use remove_reference<T&&> version. General specialization don't give you what you want - if you pass int&& to general specialiation, type will be int&&
If you want to print type, use typeid(some_type).name()
Related
I'm currently trying to understand the std::execution proposal by studying libunifex's implementation. The library makes heavy use of template metaprogramming.
There's one piece of code I really have trouble understanding:
template <class Member, class Self>
Member Self::* _memptr(const Self&);
template <typename Self, typename Member>
using member_t = decltype(
(std::declval<Self&&>() .*
_memptr<Member>(std::declval<Self&&>())));
What exactly is member_t used for? It's used in the implementation of then:
template <typename Sender, typename Receiver>
requires std::same_as<std::remove_cvref_t<Sender>, type> &&
receiver<Receiver> &&
sender_to<member_t<Sender, Predecessor>, receiver_t<std::remove_cvref_t<Receiver>>>
friend auto tag_invoke(tag_t<connect>, Sender&& s, Receiver&& r)
noexcept(
std::is_nothrow_constructible_v<std::remove_cvref_t<Receiver>, Receiver> &&
std::is_nothrow_constructible_v<Function, member_t<Sender, Function>> &&
is_nothrow_connectable_v<member_t<Sender, Predecessor>, receiver_t<std::remove_cvref_t<Receiver>>>
// ----------------------^ here
)
-> connect_result_t<member_t<Sender, Predecessor>, receiver_t<std::remove_cvref_t<Receiver>>> { /* ... */ }
_memptr is a function declared in such a way that
_memptr<Member>(self)
will give you a pointer to member of <class type of self> of type Member regardless of self's constness or value category. So if Self is possibly a reference type,
Member Self::*
would be ill-formed, but decltype(_memptr<Member>(std::declval<Self&&>())) will still give you the type you want.
The result of applying std::declval<Self&&>() .* to the member pointer is that it will produce the referenced member with the value category one would expect from a member access expression via . when the left-hand side has the value category indicated by Self's reference-qualification. That means if Self is a lvalue reference the expression will also be a lvalue and if Self is a rvalue reference or a non-reference the result will be a xvalue (rvalue).
Applying decltype to the expression gives you correspondingly a lvalue or rvalue reference to the member's type. It tells you whether, given the value category of self as a forwarding reference with template parameter Self its member of type Member should be moved from or instead copied where necessary.
Now if you have e.g.
template<typename T>
void f(T&& t) {
auto s = (member_t<T, decltype(t.s)>)(t.s);
}
where s is a non-reference non-static data member of T, then s will be copy-constructed from t.s if f is passed a lvalue and move-constructed from it if passed a rvalue. Essentially it is std::forward for the member.
In your shown code it is not directly used this way, but instead the member's type with correct reference-qualification is passed to some type trait e.g. in std::is_nothrow_constructible_v<Function, member_t<Sender, Function>> to to check whether Function can be (nothrow) constructed from a Function lvalue or rvalue depending on whether or not s was passed a lvalue or rvalue.
It think this performs an equivalent action to what std::forward_like in C++23 will do when used as
template <typename Self, typename Member>
using member_t = decltype(std::forward_like<Self>(std::declval<Member>()));
(I would not call that template metaprogramming by the way. Nothing here is using template instantiations to implement some algorithm at compile-time.)
For example if I have
#include <type_traits>
struct OwnershipReceiver
{
template <typename T,
class = typename std::enable_if
<
!std::is_lvalue_reference<T>::value
>::type
>
void receive_ownership(T&& t)
{
// taking file descriptor of t, and clear t
}
};
copied from How to make template rvalue reference parameter ONLY bind to rvalue reference?
the poster uses !std::is_lvalue_reference instead of the immediately more obvious std::is_rvalue_reference. I've verified this in my own code where the former works and the later doesn't.
Can anybody explain why the obvious doesn't work?
Because for forwarding reference, T will never be deduced as an rvalue reference. Suppose passing an object of type int to OwnershipReceiver, if the object is an lvalue, T will be deduced as an lvalue-reference, i.e. int&; if the object is an rvalue, T will be deduced as an non-reference, i.e. int. That's why std::is_rvalue_reference<T>::value won't work because it's always false.
Note that the purpose of the code is to make sure the parameter type of OwnershipReceiver is an rvalue-reference, it doesn't mean the type of T is an rvalue-reference too.
In other words, the point here it to distinguish lvalue-reference and non-reference, so !std::is_reference<T>::value works too.
BTW: If you stick to std::is_rvalue_reference, you can use std::is_rvalue_reference<T&&>::value as you found in the comment, or use it on the parameter t, e.g.
template <typename T>
auto receive_ownership(T&& t) -> typename std::enable_if<std::is_rvalue_reference<decltype(t)>::value>::type
{
// taking file descriptor of t, and clear t
}
I want a function like this:
template<typename C, typename T>
void foo(C &&aclass, T (C::*const memberFunc)(unsigned)) {
}
The parameters are (in words because C/C++ type syntax is mental):
A universal reference to a class, e.g. MyClass.
A const pointer to a member function of MyClass that takes an unsigned int and returns T.
This sort of works, however if I call it with an l-value reference as the first parameter I get an error like:
candidate template ignored: deduced conflicting types for parameter 'C' ('MyClass &' vs. 'MyClass')
As far as I understand it, it is deducing C from the first and second parameters, but comes up with different deductions and gets confused.
According to this answer you can make it only do deduction on the first parameter, and somehow use the typename keyword on the second parameter. But I can't work out the syntax to do this when I do want it to deduce one of the types in the parameter (T), but not the other (C).
This answer is also helpful but they solve it by just not using references for C at all, which in that case is equally efficient, but not in mine.
Is this possible?
With lvalues C will be deduced to be an lvalue-reference type (i.e. MyClass & for your case) for the 1st parameter, which is the expected behavior of forwarding reference; you can remove the reference-ness via std::remove_reference when using C in the 2nd parameter, e.g.
template<typename C, typename T>
void foo(C &&aclass, T (std::remove_reference_t<C>::*const memberFunc)(unsigned)) {
}
And as #Quentin pointed, using of std::remove_reference also introduces non-deduced context, that would prevent C from being deduced from the 2nd parameter.
Actually I just found that having a universal reference overload forward it to an l-value reference version works. Not particularly elegant though; I feel like there should be a better way.
template<typename C, typename T>
void foo(C &aclass, T (C::*const memberFunc)(unsigned)) {
// Code goes here.
}
template<typename C, typename T>
void foo(C &&aclass, T (C::*const memberFunc)(unsigned)) {
foo(aclass, memberFunc);
}
I've been reading Effective Modern C++ and the following thing caught my attention:
In Item 28 Scott writes:
Together, these observations about universal references and
lvalue/rvalue encoding mean that for this template
template<typename T> void func(T&& param);
the deduced template parameter T will encode
whether the argument passed to param was an lvalue or an rvalue. The
encoding mechanism is simple. When an lvalue is passed as an argument,
T is deduced to be an lvalue reference. When an rvalue is passed, T is
deduced to be a non-reference. (Note the asymmetry: lvalues are
encoded as lvalue references, but rvalues are encoded as
non-references.)
Can somebody explain why such encoding mechanism was chosen?
I mean if we will follow reference collapsing rules than usage of aforementioned template with rvalue yields rvalue reference. And as far as I can tell everything would work just the same if it were deduced as rvalue reference. Why is it encoded as non-reference?
Let's say you need to pass param around. In fact, you need to store it for lifetime purposes. As is, you'd just use T:
template <typename T>
struct store { T val; };
template<typename T> void func(T&& param) {
store<T> s{std::forward<T>(param)};
}
This works because if param got passed in by lvalue, T would be an lvalue reference type, and we're just copying the reference. If param got passed in by rvalue, we need to take ownership - T is a non-reference type, so we end up move-constructing into s.
The fact that I can just use T here as the template argument for store, instead of std::conditional_t<std::is_lvalue_reference<T>::value, T, std::remove_reference_t<T>> is probably not an accident.
I think you're thinking the wrong way around. Instead of asking "why not always make T a reference type", look at it from the other side:
Suppose you have template <typename T> void f(T&&t).
Calling it as int i; f(i);, some T is needed such that T&& resolves to int&. What's the simplest T that can achieve that? It's int&.
Calling it as f(0);, some T is needed such that T&& resolves to int&&. What's the simplest T that can achieve that? It's int, not int&&.
int&& simply doesn't really have any advantages here.
Consider this snippet of code, which uses the common idiom of having a function template construct an instance of a class template specialized on a deduced type, as seen with std::make_unique and std::make_tuple, for example:
template <typename T>
struct foo
{
std::decay_t<T> v_;
foo(T&& v) : v_(std::forward<T>(v)) {}
};
template <typename U>
foo<U> make_foo(U&& v)
{
return { std::forward<U>(v) };
}
In the context of Scott Meyers' "universal references", the argument to
make_foo is a universal reference because its type is U&& where U is
deduced. The argument to the constructor of foo is not a universal reference
because although its type is T&&, T is (in general) not deduced.
But in the case in which the constructor of foo is called by make_foo, it
seems to me that it might make sense to think of the argument to the constructor
of foo as being a universal reference, because T has been deduced by the
function template make_foo. The same reference collapsing rules will apply
so that the type of v is the same in both functions. In this case, both T
and U can be said to have been deduced.
So my question is twofold:
Does it make sense to think of the argument to the constructor of foo as being
a universal reference in the limited cases in which T has been deduced
within a universal reference context by the caller, as in my example?
In my example, are both uses of std::forward sensible?
make_foo is in the same ballpark as "right", but foo isn't. The foo constructor currently only accepts a non-deduced T &&, and forwarding there is probably not what you mean (but see #nosid's comment). All in all, foo should take a type parameter, have a templated constructor, and the maker function should do the decaying:
template <typename T>
struct foo
{
T v_;
template <typename U>
foo(U && u) : v_(std::forward<U>(u)) { }
};
template <typename U>
foo<typename std::decay<U>::type> make_foo(U && u)
{
return foo<typename std::decay<U>::type>(std::forward<U>(u));
}
In C++14 the maker function becomes a bit simpler to write:
template <typename U>
auto make_foo(U && u)
{ return foo<std::decay_t<U>>(std::forward<U>(u)); }
As your code is written now, int a; make_foo(a); would create an object of type foo<int &>. This would internally store an int, but its constructor would only accept an int & argument. By contrast, make_foo(std::move(a)) would create a foo<int>.
So the way you wrote it, the class template argument determines the signature of the constructor. (The std::forward<T>(v) still makes sense in a perverted kind of way (thanks to #nodis for pointing this out), but this is definitely not "forwarding".)
That is very unusual. Typically, the class template should determine the relevant wrapped type, and the constructor should accept anything that can be used to create the wrapped type, i.e. the constructor should be a function template.
There isn't a formal definition of "universal reference", but I would define it as:
A universal reference is a parameter of a function template with type [template-parameter] &&, with the intent that the template parameter can be deduced from the function argument, and the argument will be passed either by lvalue reference or by rvalue reference as appropriate.
So by that definition, no, the T&& v parameter in foo's constructor is not a universal reference.
However, the entire point of the phrase "universal reference" is to provide a model or pattern for us humans to think about while designing, reading, and understanding code. And it is reasonable and helpful to say that "When make_foo calls the constructor of foo<U>, the template parameter T has been deduced from the argument to make_foo in a way that allows the constructor parameter T&& v to be either an lvalue reference or an rvalue reference as appropriate." This is close enough to the same concept that I would be fine moving on to the claim: "When make_foo calls the constructor of foo<U>, the constructor parameter T&& v is essentially a universal reference."
Yes, both uses of std::forward will do what you intend here, allowing member v_ to move from the make_foo argument if possible or copy otherwise. But having make_foo(my_str) return a foo<std::string&>, not a foo<std::string>, that contains a copy of my_str is quite surprising....