According to http://en.cppreference.com/w/cpp/utility/move
std::move is declared as follows:
template <typename T>
std::remove_reference<T>::type&& move(T&& t);
As far as my understanding goes, when code is templated, the deduction of T in typename T looses information about reference, so following:
template <typename T>
void someFunction(T&& value);
when used like:
int five=5;
someFunction(five);
then
value is of type int&
T is int
or
const float value = 5.25;
someFunction(value);
then
value is of type const float&
T is const float.
If this is so, then there is no point in move declaration to declare returned type as:
std::remove_reference<T>::type&&, because T is already not a reference.
Moreover, if std::move takes as argument a reference (l-value reference in practice), then returning static_cast<T&&>(t) in std::move in fact due to reference collapsing will return l-value reference or r-value reference, so it will behave more like std::forward not move. So what is a trick, that makes it work properly, that I do not understand?
Your examples are incorrect:
int five=5;
someFunction(five);
In this case, T is deduced as int&, not int. The same goes for the second example; T is deduced as const int&.
As such, returning just T&& would mean T&& &, which is T& due to reference collapsing rules.
This is why the std::remove_reference is required, to ensure that there are no references on the type to prevent reference collapsing from taking place.
Related
I've just read this in C++ Primer :
A function parameter that is an rvalue reference to a template type
parameter (i.e., T&&) preserves the constness and lvalue/rvalue
property of its corresponding argument.
But I don't understand why T&& parameters have this feature while T&'s haven't.
What's the C++ logic behind this ?
Because if T is a function template argument, then T& is a non-constant reference to T while T&& is called a forwarding reference if used as type for a function argument. Other T&&, e.g. in template<typename T> void foo(std::vector<T&&> x) is really an r-value reference that cannot deduce const (not that const r-value references are very useful).
Since we want to automatically differentiate between the following cases:
const int x = 5; foo(x);
int x = 5; foo(std::move(x));
int x = 5; foo(x);
foo(int{5});
while retaining a single template<typename T> foo(T&& x) definition, forwarding references were given the ability to deduce const.
As to why T& cannot deduce const, it likely never was the intent of this feature. It really meant to serve as a non-constant reference to a generic type. It would make x.non_const_member() fail to compile sometimes.
Yes, that happens for T&& too but the intent here is exactly about forwarding the type as it was passed to us to somewhere else, not necessarily modifying the object ourselves.
In VS2010 std::forward is defined as such:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity appears to be used solely to disable template argument deduction. What's the point of purposefully disabling it in this case?
If you pass an rvalue reference to an object of type X to a template function that takes type T&& as its parameter, template argument deduction deduces T to be X. Therefore, the parameter has type X&&. If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.
If std::forward used template argument deduction:
Since objects with names are lvalues the only time std::forward would correctly cast to T&& would be when the input argument was an unnamed rvalue (like 7 or func()). In the case of perfect forwarding the arg you pass to std::forward is an lvalue because it has a name. std::forward's type would be deduced as an lvalue reference or const lvalue reference. Reference collapsing rules would cause the T&& in static_cast<T&&>(arg) in std::forward to always resolve as an lvalue reference or const lvalue reference.
Example:
template<typename T>
T&& forward_with_deduction(T&& obj)
{
return static_cast<T&&>(obj);
}
void test(int&){}
void test(const int&){}
void test(int&&){}
template<typename T>
void perfect_forwarder(T&& obj)
{
test(forward_with_deduction(obj));
}
int main()
{
int x;
const int& y(x);
int&& z = std::move(x);
test(forward_with_deduction(7)); // 7 is an int&&, correctly calls test(int&&)
test(forward_with_deduction(z)); // z is treated as an int&, calls test(int&)
// All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
// an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int&
// or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what
// we want in the bottom two cases.
perfect_forwarder(x);
perfect_forwarder(y);
perfect_forwarder(std::move(x));
perfect_forwarder(std::move(y));
}
Because std::forward(expr) is not useful. The only thing it can do is a no-op, i.e. perfectly-forward its argument and act like an identity function. The alternative would be that it's the same as std::move, but we already have that. In other words, assuming it were possible, in
template<typename Arg>
void generic_program(Arg&& arg)
{
std::forward(arg);
}
std::forward(arg) is semantically equivalent to arg. On the other hand, std::forward<Arg>(arg) is not a no-op in the general case.
So by forbidding std::forward(arg) it helps catch programmer errors and we lose nothing since any possible use of std::forward(arg) are trivially replaced by arg.
I think you'd understand things better if we focus on what exactly std::forward<Arg>(arg) does, rather than what std::forward(arg) would do (since it's an uninteresting no-op). Let's try to write a no-op function template that perfectly forwards its argument.
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }
This naive first attempt isn't quite valid. If we call noop(0) then NoopArg is deduced as int. This means that the return type is int&& and we can't bind such an rvalue reference from the expression arg, which is an lvalue (it's the name of a parameter). If we then attempt:
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
then int i = 0; noop(i); fails. This time, NoopArg is deduced as int& (reference collapsing rules guarantees that int& && collapses to int&), hence the return type is int&, and this time we can't bind such an lvalue reference from the expression std::move(arg) which is an xvalue.
In the context of a perfect-forwarding function like noop, sometimes we want to move, but other times we don't. The rule to know whether we should move depends on Arg: if it's not an lvalue reference type, it means noop was passed an rvalue. If it is an lvalue reference type, it means noop was passed an lvalue. So in std::forward<NoopArg>(arg), NoopArg is a necessary argument to std::forward in order for the function template to do the right thing. Without it, there's not enough information. This NoopArg is not the same type as what the T parameter of std::forward would be deduced in the general case.
Short answer:
Because for std::forward to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.
Though using std::forward inside non-template context is possible, it is pointless(, will be explained in the details).
And if anyone dares to try implementing std::forward to allow type deducing, he/she is doomed to fail painfully.
Details:
Example:
template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }
Observer that arg is declared as T&&,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), because T is a generic type, (likewise, in string s; auto && ss = s; ss is not a rvalue reference).
Thanks to universal reference, some type deduce magic happens when someFunc is being instantiated, specifically as following:
If an rvalue object, which has the type _T or _T &, is passed to someFunc, T will be deduced as _T &(, yeah, even if the type of X is just _T, please read Meyers' artical);
If an rvalue of type _T && is passed to someFunc,T will be deduced as _T &&
Now, you can replace T with the true type in above code:
When lvalue obj is passed:
auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
When rvalue obj is passed:
auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
Now, you can guess what std::forwrd does eseentially is just static_cast<T>(para)(, in fact, in clang 11's implementation it is static_cast<T &&>(para), which is the same after applying reference collapsing rule). Everything works out fine.
But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside someFunc, std::forward literally IS NOT ABLE TO deduce the original type of arg.
If you try to make the compiler do it, it will never be deduced as _T &&(, yeah, even when arg is bind to an _T &&, it is still an lvaule obj inside someFunc, hence can only be deduceed as _T or _T &.... you really should read Meyers' artical).
Last, why should you only use std::forward inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just do std::move. You simply DON'T NEED std::forward inside non-template context.
So I am trying to build a "sanitizer" function to filter formatting arguments before they are forwarded to printf.
template<typename A>
_CR_INLINE decltype(auto) sanitize_forward(A&& arg) {
return std::forward<A>(arg);
}
template<>
_CR_INLINE decltype(auto) sanitize_forward<std::string>(std::string&& arg) {
return std::forward<const char*>(arg.c_str());
}
So every std::string is supposed to "decay" into a const char*, in order to be properly formatted.
template<typename...Args>
_CR_INLINE void printf_safe(const std::string& format, Args&&...args) {
std::printf(format.c_str(), sanitize_forward<Args>(args)...);
}
I want the arguments to be perfectly forwarded to printf, which is why I return std::forward. But I can't really wrap my head around how this should be implemented.
1) Is decltype(auto) correct? It should preserve the r-value referenceness of the return of std::forward, right?
2) How should I specialize my template: std::string&& or std::string?
3) Deduced forwarding references should be the same as the actual type, right?
You almost completely misunderstand forwarding references; which is to be expected, they are confusing.
To understand them, you have to understand reference collapsing rules, std::forward itself, template argument deduction rules when deducing T&&, and how they tie together, at least to some extent.
First, reference collapsing. Look at this chart:
If Then Then
X is X& is X&& is
T T& T&&
T& T& T&
T const& T const& T const&
T&& T& T&&
if X is int, then X&& is int&&. If X is int&, then X&& is int&.
Reread that. Again. One more time. & wins over && when both are applied.
Next, deduction. I will tell lies here, but they are simplifying lies.
If you pass Foo& to a template deducing X&&, X is Foo& and X&& is Foo&.
If you pass Foo&& to a template deducing X&&, X is Foo and X&& is Foo&&.
Next, std::forward is a conditional move . For a reference variable X&& x, std::forward<X>(x) is decltype(x)(x) -- it casts x to the type it was declared as. If x is an rvalue reference, it casts x to an rvalue reference. This is needed because the type of the expression x is X& not X&& even if x is an rvalue reference. An rvalue reference is a reference to an rvalue, but is not itself an rvalue.
Now to fix your code.
template<class T>
struct tag_t{constexpr tag_t(){}};
template<class T>
constexpr tag_t<std::decay_t<T>> tag{};
template<class T>
auto sanitizer(tag_t<T>){
return [](auto&& t)->decltype(auto){
return decltype(t)(t);
};
}
template<class A>
decltype(auto) sanitize(A&& arg) {
return sanitizer(tag<A>)(std::forward<A>(arg));
}
auto sanitizer(tag_t<std::string>) {
return [](std::string const& s){return s.c_str();};
}
template<class...Ts>
void printf_safe(const std::string& format, Ts&&...ts) {
std::printf(format.c_str(), sanitize(std::forward<Ts>(ts))...);
}
I follow SRP (single responsibility principle) -- I split forward from sanitize.
Then I split which sanitizing action (sanitizer) from actually doing it (sanitize).
This let me write the std::string santization code once, without the perfect forwarding winning "by accident".
As an aside, I'd replace decay with remove ref and remove cv if you want to treat array args as non-pointers.
You can extend sanitize functionality by writing sanitizer overloads in either the namespace of tag_t or in the namespace of the type we are sanitizing (bit not in std naturally).
So I am trying to build a "sanitizer" function to filter formatting arguments before they are forwarded to printf.
Your premise seems wrong. printf is a C function which uses va_arg - it doesn't do or need any perfect forwarding.
http://en.cppreference.com/w/cpp/io/c/fprintf
It is also never going to "consume" its arguments, but merely read from them. It doesn't make sense to distinguish temporaries and non-temporaries - just take const Args&... in your printf wrapper.
1) Is decltype(auto) correct? It should preserve the r-value referenceness of the return of std::forward, right?
std::forward will either return an lvalue reference or rvalue reference. decltype(auto) will indeed preserve that.
Your specialization of sanitize_forward for std::string doesn't look useful - the std::forward<const char*> will always return a const char* &&. I don't think it's different from:
template<>
_CR_INLINE const char* sanitize_forward<std::string>(std::string&& arg) {
return arg.c_str();
}
Additionally, returning .c_str() for an rvalue reference to a std::string sounds very dangerous and incorrect: you're taking a pointer to the internal buffer of a string that's about to expire. You probably want to take const std::string& here.
2) How should I specialize my template: std::string&& or std::string?
How is it going to be called? Are you explicitly supplying a template argument? Is the template argument going to always be a non-reference, or is it going to be both an lvalue reference and a non-reference?
Since you have sanitize_forward<Args>, you will probably attempt to invoke both...
sanitize_forward<std::string>
sanitize_forward<std::string&>
...maybe with cv-qualifiers. You might want to supply an additional explicit std::decay_t<Args> parameter that deals with the "specialization" business.
3) Deduced forwarding references should be the same as the actual type, right?
Not sure what you mean by this. Could you elaborate?
In the context of a template, the following "reference collapsing" rules are applied:
template <typename T>
void foo(T && t)
{
//T& & -> T&
//T& && -> T&
//T&& & -> T&
//T&& && -> T&&
}
Why does the language prohibit "universal references" from having const qualifiers?
template <typename T>
void foo(T const && t)
It would seem to make sense if the type had resolved to a reference (3 out of the 4 cases).
I'm sure this idea is incompatible with some other design aspect of the language, but I can't quite see the full picture.
Originally the rvalue reference proposal said that the transformation happens if P is "an rvalue reference type". However, a defect report later noticed
Additionally, consider this case:
template <class T> void f(const T&&);
...
int i;
f(i);
If we deduce T as int& in this case then f(i) calls f<int&>(int&), which seems counterintuitive. We prefer that f<int>(const int&&) be called. Therefore, we would like the wording clarified that the A& deduction rule in 14.8.2.1 [temp.deduct.call] paragraph 3 applies only to the form T&& and not to cv T&& as the note currently implies.
There appears to have been a time period where const T &&, with T being U&, was transformed to const U&. That was changed to be consistent with another rule that says that const T, where T is U& would stay U& (cv-qualifiers on references are ignored). So, when you would deduce T in above example to int&, the function parameter would stay int&, not const int&.
In the defect report, the reporter states "We prefer that f<int>(const int&&) be called", however provides no reason in the defect report. I can imagine that the reason was that it seemed too intricate to fix this without introducing inconsistency with other rules, however.
We should also keep in mind that the defect report was made at a time where rvalue references could still bind to lvalues - i.e const int&& could bind to an int lvalue. This was prohibited only later on, when a paper by Dave & Doug, "A Safety Problem with RValue References", appeared. So, it seems to me that a deduction that works (at that time) was worth more than a deduction that simply was counter intuitive and dropped qualifiers.
This does already happen for references; if your T is a U const &, then T && will collapse to U const &. The term "universal reference" really does mean universal reference: you don't need to specify const in there to get a constant reference.
If you want to have a truly universal reference mechanism, you need your T && to be able to become all kinds of references, will all kinds of constness. And, T && does exactly that. It collapses to all four cases: both l- and r-value references, and both const and non-const.
Explained another way, the constness is an attribute of the type, not the reference, i.e. when you say T const &, you are actually talking about a U &, where U is T const. The same is true for && (although an r-value reference to a const is less useful).
This means that if you want your universal reference to collapse to a U const &, just pass it something that is of the type you want: a U const &, and it will collapse to exactly that.
To answer you question more directly: the language does not "prohibit" the use of const in the declaration of a universal reference, per sé. It is saying that if you change the mechanism for declaring a universal reference even a little bit - even by inserting a lowly const between the T and the && - then you won't have a (literally) "universal" reference anymore, because it just won't accept anything and everything.
Why do you think the language does not allow const r-value references?
In the following code, what will be printed?
#include <iostream>
struct Foo
{
void bar() const &
{
std::cout << "&\n";
}
void bar() const &&
{
std::cout << "&&\n";
}
};
const Foo make() {
return Foo{};
}
int main()
{
make().bar();
}
answer:
&&
why? Because make() returns a const object and in this context it's a temporary. Therefore r-value reference to const.
Template argument deduction has a special case for "rvalue reference to cv-unqualified template parameters". It is this very special case that forwarding/universal references rely on. See section "Deduction from a function call" in the linked article for details.
Note that before template argument deduction, all top-level cv-qualifiers are removed; however, references never have top-level cv-qualifiers and above rule does not apply, so the special rule also does not apply. (In contrast to pointers, there is no "const reference", only "reference to const")
In VS2010 std::forward is defined as such:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity appears to be used solely to disable template argument deduction. What's the point of purposefully disabling it in this case?
If you pass an rvalue reference to an object of type X to a template function that takes type T&& as its parameter, template argument deduction deduces T to be X. Therefore, the parameter has type X&&. If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.
If std::forward used template argument deduction:
Since objects with names are lvalues the only time std::forward would correctly cast to T&& would be when the input argument was an unnamed rvalue (like 7 or func()). In the case of perfect forwarding the arg you pass to std::forward is an lvalue because it has a name. std::forward's type would be deduced as an lvalue reference or const lvalue reference. Reference collapsing rules would cause the T&& in static_cast<T&&>(arg) in std::forward to always resolve as an lvalue reference or const lvalue reference.
Example:
template<typename T>
T&& forward_with_deduction(T&& obj)
{
return static_cast<T&&>(obj);
}
void test(int&){}
void test(const int&){}
void test(int&&){}
template<typename T>
void perfect_forwarder(T&& obj)
{
test(forward_with_deduction(obj));
}
int main()
{
int x;
const int& y(x);
int&& z = std::move(x);
test(forward_with_deduction(7)); // 7 is an int&&, correctly calls test(int&&)
test(forward_with_deduction(z)); // z is treated as an int&, calls test(int&)
// All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
// an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int&
// or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what
// we want in the bottom two cases.
perfect_forwarder(x);
perfect_forwarder(y);
perfect_forwarder(std::move(x));
perfect_forwarder(std::move(y));
}
Because std::forward(expr) is not useful. The only thing it can do is a no-op, i.e. perfectly-forward its argument and act like an identity function. The alternative would be that it's the same as std::move, but we already have that. In other words, assuming it were possible, in
template<typename Arg>
void generic_program(Arg&& arg)
{
std::forward(arg);
}
std::forward(arg) is semantically equivalent to arg. On the other hand, std::forward<Arg>(arg) is not a no-op in the general case.
So by forbidding std::forward(arg) it helps catch programmer errors and we lose nothing since any possible use of std::forward(arg) are trivially replaced by arg.
I think you'd understand things better if we focus on what exactly std::forward<Arg>(arg) does, rather than what std::forward(arg) would do (since it's an uninteresting no-op). Let's try to write a no-op function template that perfectly forwards its argument.
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }
This naive first attempt isn't quite valid. If we call noop(0) then NoopArg is deduced as int. This means that the return type is int&& and we can't bind such an rvalue reference from the expression arg, which is an lvalue (it's the name of a parameter). If we then attempt:
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
then int i = 0; noop(i); fails. This time, NoopArg is deduced as int& (reference collapsing rules guarantees that int& && collapses to int&), hence the return type is int&, and this time we can't bind such an lvalue reference from the expression std::move(arg) which is an xvalue.
In the context of a perfect-forwarding function like noop, sometimes we want to move, but other times we don't. The rule to know whether we should move depends on Arg: if it's not an lvalue reference type, it means noop was passed an rvalue. If it is an lvalue reference type, it means noop was passed an lvalue. So in std::forward<NoopArg>(arg), NoopArg is a necessary argument to std::forward in order for the function template to do the right thing. Without it, there's not enough information. This NoopArg is not the same type as what the T parameter of std::forward would be deduced in the general case.
Short answer:
Because for std::forward to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.
Though using std::forward inside non-template context is possible, it is pointless(, will be explained in the details).
And if anyone dares to try implementing std::forward to allow type deducing, he/she is doomed to fail painfully.
Details:
Example:
template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }
Observer that arg is declared as T&&,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), because T is a generic type, (likewise, in string s; auto && ss = s; ss is not a rvalue reference).
Thanks to universal reference, some type deduce magic happens when someFunc is being instantiated, specifically as following:
If an rvalue object, which has the type _T or _T &, is passed to someFunc, T will be deduced as _T &(, yeah, even if the type of X is just _T, please read Meyers' artical);
If an rvalue of type _T && is passed to someFunc,T will be deduced as _T &&
Now, you can replace T with the true type in above code:
When lvalue obj is passed:
auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
When rvalue obj is passed:
auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
Now, you can guess what std::forwrd does eseentially is just static_cast<T>(para)(, in fact, in clang 11's implementation it is static_cast<T &&>(para), which is the same after applying reference collapsing rule). Everything works out fine.
But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside someFunc, std::forward literally IS NOT ABLE TO deduce the original type of arg.
If you try to make the compiler do it, it will never be deduced as _T &&(, yeah, even when arg is bind to an _T &&, it is still an lvaule obj inside someFunc, hence can only be deduceed as _T or _T &.... you really should read Meyers' artical).
Last, why should you only use std::forward inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just do std::move. You simply DON'T NEED std::forward inside non-template context.