Why use std::forward instead of static_cast
up vote
19
down vote
favorite
When given code of the following structure
template <typename... Args>
void foo(Args&&... args) { ... }
I've often seen library code use static_cast<Args&&>
within the function for argument forwarding. Typically, the justification for this is that using a static_cast
avoids an unnecessary template instantiation.
Given the language's reference collapsing and template deduction rules. We get perfect forwarding with the static_cast<Args&&>
, the proof for this claim is below (within error margins, which I am hoping an answer will enlighten)
- When given rvalue references (or for completeness - no reference qualification as in this example), this collapses the references in such a way that the result is an rvalue. The rule used is
&& &&
->&&
(rule1
above) - When given lvalue references, this collapses the references in such a way that the result is an lvalue. The rule used here is
& &&
->&
(rule2
above)
This is essentially getting foo()
to forward the arguments to bar()
in the example above. This is the behavior you would get when using std::forward<Args>
here as well.
Question - why use std::forward
in these contexts at all? Does avoiding the extra instantiation justify breaking convention?
Howard Hinnant's paper n2951 specified 6 constraints under which any implementation of std::forward
should behave "correctly". These were
- Should forward an lvalue as an lvalue
- Should forward an rvalue as an rvalue
- Should not forward an rvalue as an lvalue
- Should forward less cv-qualified expressions to more cv-qualified expressions
- Should forward expressions of derived type to an accessible, unambiguous base type
- Should not forward arbitrary type conversions
(1) and (2) were proven to work correctly with static_cast<Args&&>
above. (3) - (6) don't apply here because when functions are called in a deduced context, none of these can occur.
Note: I personally prefer to use std::forward
, but the justification I have is purely that I prefer to stick to convention.
c++ templates c++17 rvalue-reference forwarding
|
show 5 more comments
up vote
19
down vote
favorite
When given code of the following structure
template <typename... Args>
void foo(Args&&... args) { ... }
I've often seen library code use static_cast<Args&&>
within the function for argument forwarding. Typically, the justification for this is that using a static_cast
avoids an unnecessary template instantiation.
Given the language's reference collapsing and template deduction rules. We get perfect forwarding with the static_cast<Args&&>
, the proof for this claim is below (within error margins, which I am hoping an answer will enlighten)
- When given rvalue references (or for completeness - no reference qualification as in this example), this collapses the references in such a way that the result is an rvalue. The rule used is
&& &&
->&&
(rule1
above) - When given lvalue references, this collapses the references in such a way that the result is an lvalue. The rule used here is
& &&
->&
(rule2
above)
This is essentially getting foo()
to forward the arguments to bar()
in the example above. This is the behavior you would get when using std::forward<Args>
here as well.
Question - why use std::forward
in these contexts at all? Does avoiding the extra instantiation justify breaking convention?
Howard Hinnant's paper n2951 specified 6 constraints under which any implementation of std::forward
should behave "correctly". These were
- Should forward an lvalue as an lvalue
- Should forward an rvalue as an rvalue
- Should not forward an rvalue as an lvalue
- Should forward less cv-qualified expressions to more cv-qualified expressions
- Should forward expressions of derived type to an accessible, unambiguous base type
- Should not forward arbitrary type conversions
(1) and (2) were proven to work correctly with static_cast<Args&&>
above. (3) - (6) don't apply here because when functions are called in a deduced context, none of these can occur.
Note: I personally prefer to use std::forward
, but the justification I have is purely that I prefer to stick to convention.
c++ templates c++17 rvalue-reference forwarding
1
I would think in some template library, people avoid usingstd::forward
because they want the library be self-contained, and don't want to use standard library. They will also implement their ownstd::move
, etc.
– liliscent
Nov 12 at 8:06
1
@VTT Not in this case, because lvalues are typically deduced asT&
- wandbox.org/permlink/hPucHiFB2pwh53Ox
– Curious
Nov 12 at 8:09
8
std::forward
andstd::move
are for readability, you could usestatic_cast
instead to get the same behaviour
– M.M
Nov 12 at 8:12
What M.M said. In this answer, Howard mentionsstd::move
was introduced to make the proposal more palatable. Then it stuck. I would imagine the same goes forstd::forward
.
– StoryTeller
Nov 12 at 8:40
@M.M Your point about readability applies. However, std::move is slightly different in my mind because I don’t see the implementation going out of it’s way to prevent against user errors, making it a simpler 1-1 mapping of a cast. The fact that std::forward typically has two overloads, presumably for safety, makes me think that this situation is potentially more complex. Although, I don’t have a good reason to justify that. Hence, the question I guess.
– Curious
Nov 12 at 8:41
|
show 5 more comments
up vote
19
down vote
favorite
up vote
19
down vote
favorite
When given code of the following structure
template <typename... Args>
void foo(Args&&... args) { ... }
I've often seen library code use static_cast<Args&&>
within the function for argument forwarding. Typically, the justification for this is that using a static_cast
avoids an unnecessary template instantiation.
Given the language's reference collapsing and template deduction rules. We get perfect forwarding with the static_cast<Args&&>
, the proof for this claim is below (within error margins, which I am hoping an answer will enlighten)
- When given rvalue references (or for completeness - no reference qualification as in this example), this collapses the references in such a way that the result is an rvalue. The rule used is
&& &&
->&&
(rule1
above) - When given lvalue references, this collapses the references in such a way that the result is an lvalue. The rule used here is
& &&
->&
(rule2
above)
This is essentially getting foo()
to forward the arguments to bar()
in the example above. This is the behavior you would get when using std::forward<Args>
here as well.
Question - why use std::forward
in these contexts at all? Does avoiding the extra instantiation justify breaking convention?
Howard Hinnant's paper n2951 specified 6 constraints under which any implementation of std::forward
should behave "correctly". These were
- Should forward an lvalue as an lvalue
- Should forward an rvalue as an rvalue
- Should not forward an rvalue as an lvalue
- Should forward less cv-qualified expressions to more cv-qualified expressions
- Should forward expressions of derived type to an accessible, unambiguous base type
- Should not forward arbitrary type conversions
(1) and (2) were proven to work correctly with static_cast<Args&&>
above. (3) - (6) don't apply here because when functions are called in a deduced context, none of these can occur.
Note: I personally prefer to use std::forward
, but the justification I have is purely that I prefer to stick to convention.
c++ templates c++17 rvalue-reference forwarding
When given code of the following structure
template <typename... Args>
void foo(Args&&... args) { ... }
I've often seen library code use static_cast<Args&&>
within the function for argument forwarding. Typically, the justification for this is that using a static_cast
avoids an unnecessary template instantiation.
Given the language's reference collapsing and template deduction rules. We get perfect forwarding with the static_cast<Args&&>
, the proof for this claim is below (within error margins, which I am hoping an answer will enlighten)
- When given rvalue references (or for completeness - no reference qualification as in this example), this collapses the references in such a way that the result is an rvalue. The rule used is
&& &&
->&&
(rule1
above) - When given lvalue references, this collapses the references in such a way that the result is an lvalue. The rule used here is
& &&
->&
(rule2
above)
This is essentially getting foo()
to forward the arguments to bar()
in the example above. This is the behavior you would get when using std::forward<Args>
here as well.
Question - why use std::forward
in these contexts at all? Does avoiding the extra instantiation justify breaking convention?
Howard Hinnant's paper n2951 specified 6 constraints under which any implementation of std::forward
should behave "correctly". These were
- Should forward an lvalue as an lvalue
- Should forward an rvalue as an rvalue
- Should not forward an rvalue as an lvalue
- Should forward less cv-qualified expressions to more cv-qualified expressions
- Should forward expressions of derived type to an accessible, unambiguous base type
- Should not forward arbitrary type conversions
(1) and (2) were proven to work correctly with static_cast<Args&&>
above. (3) - (6) don't apply here because when functions are called in a deduced context, none of these can occur.
Note: I personally prefer to use std::forward
, but the justification I have is purely that I prefer to stick to convention.
c++ templates c++17 rvalue-reference forwarding
c++ templates c++17 rvalue-reference forwarding
edited Nov 12 at 8:01
asked Nov 12 at 7:50
Curious
11.8k22469
11.8k22469
1
I would think in some template library, people avoid usingstd::forward
because they want the library be self-contained, and don't want to use standard library. They will also implement their ownstd::move
, etc.
– liliscent
Nov 12 at 8:06
1
@VTT Not in this case, because lvalues are typically deduced asT&
- wandbox.org/permlink/hPucHiFB2pwh53Ox
– Curious
Nov 12 at 8:09
8
std::forward
andstd::move
are for readability, you could usestatic_cast
instead to get the same behaviour
– M.M
Nov 12 at 8:12
What M.M said. In this answer, Howard mentionsstd::move
was introduced to make the proposal more palatable. Then it stuck. I would imagine the same goes forstd::forward
.
– StoryTeller
Nov 12 at 8:40
@M.M Your point about readability applies. However, std::move is slightly different in my mind because I don’t see the implementation going out of it’s way to prevent against user errors, making it a simpler 1-1 mapping of a cast. The fact that std::forward typically has two overloads, presumably for safety, makes me think that this situation is potentially more complex. Although, I don’t have a good reason to justify that. Hence, the question I guess.
– Curious
Nov 12 at 8:41
|
show 5 more comments
1
I would think in some template library, people avoid usingstd::forward
because they want the library be self-contained, and don't want to use standard library. They will also implement their ownstd::move
, etc.
– liliscent
Nov 12 at 8:06
1
@VTT Not in this case, because lvalues are typically deduced asT&
- wandbox.org/permlink/hPucHiFB2pwh53Ox
– Curious
Nov 12 at 8:09
8
std::forward
andstd::move
are for readability, you could usestatic_cast
instead to get the same behaviour
– M.M
Nov 12 at 8:12
What M.M said. In this answer, Howard mentionsstd::move
was introduced to make the proposal more palatable. Then it stuck. I would imagine the same goes forstd::forward
.
– StoryTeller
Nov 12 at 8:40
@M.M Your point about readability applies. However, std::move is slightly different in my mind because I don’t see the implementation going out of it’s way to prevent against user errors, making it a simpler 1-1 mapping of a cast. The fact that std::forward typically has two overloads, presumably for safety, makes me think that this situation is potentially more complex. Although, I don’t have a good reason to justify that. Hence, the question I guess.
– Curious
Nov 12 at 8:41
1
1
I would think in some template library, people avoid using
std::forward
because they want the library be self-contained, and don't want to use standard library. They will also implement their own std::move
, etc.– liliscent
Nov 12 at 8:06
I would think in some template library, people avoid using
std::forward
because they want the library be self-contained, and don't want to use standard library. They will also implement their own std::move
, etc.– liliscent
Nov 12 at 8:06
1
1
@VTT Not in this case, because lvalues are typically deduced as
T&
- wandbox.org/permlink/hPucHiFB2pwh53Ox– Curious
Nov 12 at 8:09
@VTT Not in this case, because lvalues are typically deduced as
T&
- wandbox.org/permlink/hPucHiFB2pwh53Ox– Curious
Nov 12 at 8:09
8
8
std::forward
and std::move
are for readability, you could use static_cast
instead to get the same behaviour– M.M
Nov 12 at 8:12
std::forward
and std::move
are for readability, you could use static_cast
instead to get the same behaviour– M.M
Nov 12 at 8:12
What M.M said. In this answer, Howard mentions
std::move
was introduced to make the proposal more palatable. Then it stuck. I would imagine the same goes for std::forward
.– StoryTeller
Nov 12 at 8:40
What M.M said. In this answer, Howard mentions
std::move
was introduced to make the proposal more palatable. Then it stuck. I would imagine the same goes for std::forward
.– StoryTeller
Nov 12 at 8:40
@M.M Your point about readability applies. However, std::move is slightly different in my mind because I don’t see the implementation going out of it’s way to prevent against user errors, making it a simpler 1-1 mapping of a cast. The fact that std::forward typically has two overloads, presumably for safety, makes me think that this situation is potentially more complex. Although, I don’t have a good reason to justify that. Hence, the question I guess.
– Curious
Nov 12 at 8:41
@M.M Your point about readability applies. However, std::move is slightly different in my mind because I don’t see the implementation going out of it’s way to prevent against user errors, making it a simpler 1-1 mapping of a cast. The fact that std::forward typically has two overloads, presumably for safety, makes me think that this situation is potentially more complex. Although, I don’t have a good reason to justify that. Hence, the question I guess.
– Curious
Nov 12 at 8:41
|
show 5 more comments
3 Answers
3
active
oldest
votes
up vote
5
down vote
accepted
forward
expresses the intent and it may be safer to use than static_cast
: static_cast
considers conversion but some dangerous and supposedly non-intentional conversions are detected with forward
:
struct A{
A(int);
};
template<class Arg1,class Arg2>
Arg1&& f(Arg1&& a1,Arg2&& a2){
return static_cast<Arg1&&>(a2); // typing error: a1=>a2
}
template<class Arg1,class Arg2>
Arg1&& g(Arg1&& a1,Arg2&& a2){
return forward<Arg1>(a2); // typing error: a1=>a2
}
void test(const A a,int i){
const A& x = f(a,i);//dangling reference
const A& y = g(a,i);//compilation error
}
Example of error message: compiler explorer link
How applies this justification: Typically, the justification for this is that using a static_cast avoids an unnecessary template instantiation.
Is the compilation time more problematic than code maintainability? Should the coder even lose its time considering minimizing "unnecessary template instantiation" at every line in the code?
When a template is instantiated, its instantiation causes instantiations of template that are used in its definition and declaration. So that, for example if you have a function as:
template<class T> void foo(T i){
foo_1(i),foo_2(i),foo_3(i);
}
where foo_1
,foo_2
,foo_3
are templates, the instantiation of foo
will cause 3 instantiations. Then recursively if those functions cause the instantiation of other 3 template functions, you could get 3*3=9 instantiations for example. So you can consider this chain of instantiation as a tree where a root function instantiation can cause thousands of instantiations as an exponentially growing ripple effect. On the other hand a function like forward
is a leaf in this instantiation tree. So avoiding its instantiation may only avoid 1 instantiation.
So, the best way to avoid template instantiation explosion is to use dynamic polymorphism for "root" classes and type of argument of "root" functions and then use static polymorphism only for time critical functions that are virtually upper in this instantiation tree.
So, in my opinion using static_cast
in place forward
to avoid instantiations is a lost of time compared to the benefit of using a more expressive (and safer) code. Template instantiation explosion is more efficiently managed at code architecture level.
It's not the conversion that is being checked, the check is merely against rvalue -> lvalue forwarding. The code above will compile ifg(a, i)
is changed tog(std::move(a), i)
.
– liliscent
Nov 12 at 12:12
2
How is this brace-style called?
– YSC
Nov 12 at 13:04
2
@YSC: seems en.wikipedia.org/wiki/Indentation_style#Ratliff_style
– geza
Nov 12 at 14:33
@liliscent Not necessarily, you can make this work, like you said, but the conversion is prevented in this case. And I see that as a consequence ofstd::forward
's implementation. It doesn't prevent all possible footguns, but at least a few of them. This is the only difference betweenstatic_cast
andstd::forward
. So it makes sense as a motivating reason to usestd::forward
overstatic_cast
to me.
– Curious
23 hours ago
add a comment |
up vote
12
down vote
Scott Meyers says that std::forward
and std::move
are mainly for convenience. He even states that std::forward
can be used to perform the functionality of both std::forward
and std::move
.
Some excerpts from "Effective Modern C++":
Item 23:Understand std::move and std::forward
...
The story forstd::forward
is similar to that forstd::move
, but whereasstd::move
unconditionally casts its argument to anrvalue
,std::forward
does it only under certain conditions.std::forward
is a conditional cast. It casts to anrvalue
only if its argument was initialized with anrvalue
.
...
Given that bothstd::move
andstd::forward
boil down to casts, the only difference being thatstd::move
always casts, whilestd::forward
only sometimes does, you might ask whether we can dispense withstd::move
and just usestd::forward
everywhere. From a purely technical perspective, the answer is yes:std::forward
can do it all.std::move
isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky.
...std::move
’s attractions are convenience, reduced likelihood of error, and greater clarity...
For those interested, comparison of std::forward<T>
vs static_cast<T&&>
in assembly (without any optimization) when called with lvalue
and rvalue
.
If I want to change an lvalue to an rvalue, I have to usestd::move
, but notstd::forward
. Right?
– Yongwei Wu
Nov 12 at 10:23
Yes. move casts its argument to an rvalue.
– P.W
Nov 12 at 10:31
2
Sort of expected this comment from you. :-) I don't think that scott meyers meant that we can just replace move with forward. He gives an example in that item implementing move in terms of forward. " std::move requires only a function argument (rhs.s), while std::forward requires both a function argument (rhs.s) and a template type argument (std::string). Then note that the type we pass to std::forward should be a non-reference, because that’s the convention for encoding that the argument being passed is an rvalue (see Item 28). contd...
– P.W
Nov 12 at 10:43
3
...Together, this means that std::move requires less typing than std::forward, and it spares us the trouble of passing a type argument that encodes that the argument we’re passing is an rvalue. It also eliminates the possibility of our passing an incorrect type (e.g., std::string&, which would result in the data member s being copy constructed instead of move constructed). More importantly, the use of std::move conveys an unconditional cast to an rvalue, while the use of std::forward indicates a cast to an rvalue only for references to which rvalues have been bound.
– P.W
Nov 12 at 10:44
1
@M.M: Yes, looks like Scott Meyers did not take that into account. I was quoting him verbtim . :)
– P.W
Nov 12 at 11:50
|
show 3 more comments
up vote
5
down vote
Here are my 2.5, not so technical cents for this: the fact that today std::forward
is indeed just a plain old static_cast<T&&>
does not mean that tomorrow it also will be implemented in exactly the same way. I think the committee needed something to reflect the desired behaviour of what std::forward
achieves today hence, the forward
which does not forward anything anywhere came into existence.
With having the required behaviour and expectations formalized under the umbrella of std::forward
, just theoretically speaking, noone impedes a future implementer to provide the std::forward
as not the static_cast<T&&>
but something specific his own implementation, without actually taking into consideration static_cast<T&&>
because the only fact that matters is the correct usage and behaviour of std::forward
.
I sincerely doubt thatforward
will ever do something else. Think of how much code that will break...
– Rakete1111
Nov 12 at 12:07
1
@Rakete1111 That is why my answer is just hypothetical, highly debatable and pure virtual :)
– fritzone
Nov 12 at 12:14
add a comment |
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
5
down vote
accepted
forward
expresses the intent and it may be safer to use than static_cast
: static_cast
considers conversion but some dangerous and supposedly non-intentional conversions are detected with forward
:
struct A{
A(int);
};
template<class Arg1,class Arg2>
Arg1&& f(Arg1&& a1,Arg2&& a2){
return static_cast<Arg1&&>(a2); // typing error: a1=>a2
}
template<class Arg1,class Arg2>
Arg1&& g(Arg1&& a1,Arg2&& a2){
return forward<Arg1>(a2); // typing error: a1=>a2
}
void test(const A a,int i){
const A& x = f(a,i);//dangling reference
const A& y = g(a,i);//compilation error
}
Example of error message: compiler explorer link
How applies this justification: Typically, the justification for this is that using a static_cast avoids an unnecessary template instantiation.
Is the compilation time more problematic than code maintainability? Should the coder even lose its time considering minimizing "unnecessary template instantiation" at every line in the code?
When a template is instantiated, its instantiation causes instantiations of template that are used in its definition and declaration. So that, for example if you have a function as:
template<class T> void foo(T i){
foo_1(i),foo_2(i),foo_3(i);
}
where foo_1
,foo_2
,foo_3
are templates, the instantiation of foo
will cause 3 instantiations. Then recursively if those functions cause the instantiation of other 3 template functions, you could get 3*3=9 instantiations for example. So you can consider this chain of instantiation as a tree where a root function instantiation can cause thousands of instantiations as an exponentially growing ripple effect. On the other hand a function like forward
is a leaf in this instantiation tree. So avoiding its instantiation may only avoid 1 instantiation.
So, the best way to avoid template instantiation explosion is to use dynamic polymorphism for "root" classes and type of argument of "root" functions and then use static polymorphism only for time critical functions that are virtually upper in this instantiation tree.
So, in my opinion using static_cast
in place forward
to avoid instantiations is a lost of time compared to the benefit of using a more expressive (and safer) code. Template instantiation explosion is more efficiently managed at code architecture level.
It's not the conversion that is being checked, the check is merely against rvalue -> lvalue forwarding. The code above will compile ifg(a, i)
is changed tog(std::move(a), i)
.
– liliscent
Nov 12 at 12:12
2
How is this brace-style called?
– YSC
Nov 12 at 13:04
2
@YSC: seems en.wikipedia.org/wiki/Indentation_style#Ratliff_style
– geza
Nov 12 at 14:33
@liliscent Not necessarily, you can make this work, like you said, but the conversion is prevented in this case. And I see that as a consequence ofstd::forward
's implementation. It doesn't prevent all possible footguns, but at least a few of them. This is the only difference betweenstatic_cast
andstd::forward
. So it makes sense as a motivating reason to usestd::forward
overstatic_cast
to me.
– Curious
23 hours ago
add a comment |
up vote
5
down vote
accepted
forward
expresses the intent and it may be safer to use than static_cast
: static_cast
considers conversion but some dangerous and supposedly non-intentional conversions are detected with forward
:
struct A{
A(int);
};
template<class Arg1,class Arg2>
Arg1&& f(Arg1&& a1,Arg2&& a2){
return static_cast<Arg1&&>(a2); // typing error: a1=>a2
}
template<class Arg1,class Arg2>
Arg1&& g(Arg1&& a1,Arg2&& a2){
return forward<Arg1>(a2); // typing error: a1=>a2
}
void test(const A a,int i){
const A& x = f(a,i);//dangling reference
const A& y = g(a,i);//compilation error
}
Example of error message: compiler explorer link
How applies this justification: Typically, the justification for this is that using a static_cast avoids an unnecessary template instantiation.
Is the compilation time more problematic than code maintainability? Should the coder even lose its time considering minimizing "unnecessary template instantiation" at every line in the code?
When a template is instantiated, its instantiation causes instantiations of template that are used in its definition and declaration. So that, for example if you have a function as:
template<class T> void foo(T i){
foo_1(i),foo_2(i),foo_3(i);
}
where foo_1
,foo_2
,foo_3
are templates, the instantiation of foo
will cause 3 instantiations. Then recursively if those functions cause the instantiation of other 3 template functions, you could get 3*3=9 instantiations for example. So you can consider this chain of instantiation as a tree where a root function instantiation can cause thousands of instantiations as an exponentially growing ripple effect. On the other hand a function like forward
is a leaf in this instantiation tree. So avoiding its instantiation may only avoid 1 instantiation.
So, the best way to avoid template instantiation explosion is to use dynamic polymorphism for "root" classes and type of argument of "root" functions and then use static polymorphism only for time critical functions that are virtually upper in this instantiation tree.
So, in my opinion using static_cast
in place forward
to avoid instantiations is a lost of time compared to the benefit of using a more expressive (and safer) code. Template instantiation explosion is more efficiently managed at code architecture level.
It's not the conversion that is being checked, the check is merely against rvalue -> lvalue forwarding. The code above will compile ifg(a, i)
is changed tog(std::move(a), i)
.
– liliscent
Nov 12 at 12:12
2
How is this brace-style called?
– YSC
Nov 12 at 13:04
2
@YSC: seems en.wikipedia.org/wiki/Indentation_style#Ratliff_style
– geza
Nov 12 at 14:33
@liliscent Not necessarily, you can make this work, like you said, but the conversion is prevented in this case. And I see that as a consequence ofstd::forward
's implementation. It doesn't prevent all possible footguns, but at least a few of them. This is the only difference betweenstatic_cast
andstd::forward
. So it makes sense as a motivating reason to usestd::forward
overstatic_cast
to me.
– Curious
23 hours ago
add a comment |
up vote
5
down vote
accepted
up vote
5
down vote
accepted
forward
expresses the intent and it may be safer to use than static_cast
: static_cast
considers conversion but some dangerous and supposedly non-intentional conversions are detected with forward
:
struct A{
A(int);
};
template<class Arg1,class Arg2>
Arg1&& f(Arg1&& a1,Arg2&& a2){
return static_cast<Arg1&&>(a2); // typing error: a1=>a2
}
template<class Arg1,class Arg2>
Arg1&& g(Arg1&& a1,Arg2&& a2){
return forward<Arg1>(a2); // typing error: a1=>a2
}
void test(const A a,int i){
const A& x = f(a,i);//dangling reference
const A& y = g(a,i);//compilation error
}
Example of error message: compiler explorer link
How applies this justification: Typically, the justification for this is that using a static_cast avoids an unnecessary template instantiation.
Is the compilation time more problematic than code maintainability? Should the coder even lose its time considering minimizing "unnecessary template instantiation" at every line in the code?
When a template is instantiated, its instantiation causes instantiations of template that are used in its definition and declaration. So that, for example if you have a function as:
template<class T> void foo(T i){
foo_1(i),foo_2(i),foo_3(i);
}
where foo_1
,foo_2
,foo_3
are templates, the instantiation of foo
will cause 3 instantiations. Then recursively if those functions cause the instantiation of other 3 template functions, you could get 3*3=9 instantiations for example. So you can consider this chain of instantiation as a tree where a root function instantiation can cause thousands of instantiations as an exponentially growing ripple effect. On the other hand a function like forward
is a leaf in this instantiation tree. So avoiding its instantiation may only avoid 1 instantiation.
So, the best way to avoid template instantiation explosion is to use dynamic polymorphism for "root" classes and type of argument of "root" functions and then use static polymorphism only for time critical functions that are virtually upper in this instantiation tree.
So, in my opinion using static_cast
in place forward
to avoid instantiations is a lost of time compared to the benefit of using a more expressive (and safer) code. Template instantiation explosion is more efficiently managed at code architecture level.
forward
expresses the intent and it may be safer to use than static_cast
: static_cast
considers conversion but some dangerous and supposedly non-intentional conversions are detected with forward
:
struct A{
A(int);
};
template<class Arg1,class Arg2>
Arg1&& f(Arg1&& a1,Arg2&& a2){
return static_cast<Arg1&&>(a2); // typing error: a1=>a2
}
template<class Arg1,class Arg2>
Arg1&& g(Arg1&& a1,Arg2&& a2){
return forward<Arg1>(a2); // typing error: a1=>a2
}
void test(const A a,int i){
const A& x = f(a,i);//dangling reference
const A& y = g(a,i);//compilation error
}
Example of error message: compiler explorer link
How applies this justification: Typically, the justification for this is that using a static_cast avoids an unnecessary template instantiation.
Is the compilation time more problematic than code maintainability? Should the coder even lose its time considering minimizing "unnecessary template instantiation" at every line in the code?
When a template is instantiated, its instantiation causes instantiations of template that are used in its definition and declaration. So that, for example if you have a function as:
template<class T> void foo(T i){
foo_1(i),foo_2(i),foo_3(i);
}
where foo_1
,foo_2
,foo_3
are templates, the instantiation of foo
will cause 3 instantiations. Then recursively if those functions cause the instantiation of other 3 template functions, you could get 3*3=9 instantiations for example. So you can consider this chain of instantiation as a tree where a root function instantiation can cause thousands of instantiations as an exponentially growing ripple effect. On the other hand a function like forward
is a leaf in this instantiation tree. So avoiding its instantiation may only avoid 1 instantiation.
So, the best way to avoid template instantiation explosion is to use dynamic polymorphism for "root" classes and type of argument of "root" functions and then use static polymorphism only for time critical functions that are virtually upper in this instantiation tree.
So, in my opinion using static_cast
in place forward
to avoid instantiations is a lost of time compared to the benefit of using a more expressive (and safer) code. Template instantiation explosion is more efficiently managed at code architecture level.
edited Nov 12 at 18:28
answered Nov 12 at 10:40
Oliv
7,4591752
7,4591752
It's not the conversion that is being checked, the check is merely against rvalue -> lvalue forwarding. The code above will compile ifg(a, i)
is changed tog(std::move(a), i)
.
– liliscent
Nov 12 at 12:12
2
How is this brace-style called?
– YSC
Nov 12 at 13:04
2
@YSC: seems en.wikipedia.org/wiki/Indentation_style#Ratliff_style
– geza
Nov 12 at 14:33
@liliscent Not necessarily, you can make this work, like you said, but the conversion is prevented in this case. And I see that as a consequence ofstd::forward
's implementation. It doesn't prevent all possible footguns, but at least a few of them. This is the only difference betweenstatic_cast
andstd::forward
. So it makes sense as a motivating reason to usestd::forward
overstatic_cast
to me.
– Curious
23 hours ago
add a comment |
It's not the conversion that is being checked, the check is merely against rvalue -> lvalue forwarding. The code above will compile ifg(a, i)
is changed tog(std::move(a), i)
.
– liliscent
Nov 12 at 12:12
2
How is this brace-style called?
– YSC
Nov 12 at 13:04
2
@YSC: seems en.wikipedia.org/wiki/Indentation_style#Ratliff_style
– geza
Nov 12 at 14:33
@liliscent Not necessarily, you can make this work, like you said, but the conversion is prevented in this case. And I see that as a consequence ofstd::forward
's implementation. It doesn't prevent all possible footguns, but at least a few of them. This is the only difference betweenstatic_cast
andstd::forward
. So it makes sense as a motivating reason to usestd::forward
overstatic_cast
to me.
– Curious
23 hours ago
It's not the conversion that is being checked, the check is merely against rvalue -> lvalue forwarding. The code above will compile if
g(a, i)
is changed to g(std::move(a), i)
.– liliscent
Nov 12 at 12:12
It's not the conversion that is being checked, the check is merely against rvalue -> lvalue forwarding. The code above will compile if
g(a, i)
is changed to g(std::move(a), i)
.– liliscent
Nov 12 at 12:12
2
2
How is this brace-style called?
– YSC
Nov 12 at 13:04
How is this brace-style called?
– YSC
Nov 12 at 13:04
2
2
@YSC: seems en.wikipedia.org/wiki/Indentation_style#Ratliff_style
– geza
Nov 12 at 14:33
@YSC: seems en.wikipedia.org/wiki/Indentation_style#Ratliff_style
– geza
Nov 12 at 14:33
@liliscent Not necessarily, you can make this work, like you said, but the conversion is prevented in this case. And I see that as a consequence of
std::forward
's implementation. It doesn't prevent all possible footguns, but at least a few of them. This is the only difference between static_cast
and std::forward
. So it makes sense as a motivating reason to use std::forward
over static_cast
to me.– Curious
23 hours ago
@liliscent Not necessarily, you can make this work, like you said, but the conversion is prevented in this case. And I see that as a consequence of
std::forward
's implementation. It doesn't prevent all possible footguns, but at least a few of them. This is the only difference between static_cast
and std::forward
. So it makes sense as a motivating reason to use std::forward
over static_cast
to me.– Curious
23 hours ago
add a comment |
up vote
12
down vote
Scott Meyers says that std::forward
and std::move
are mainly for convenience. He even states that std::forward
can be used to perform the functionality of both std::forward
and std::move
.
Some excerpts from "Effective Modern C++":
Item 23:Understand std::move and std::forward
...
The story forstd::forward
is similar to that forstd::move
, but whereasstd::move
unconditionally casts its argument to anrvalue
,std::forward
does it only under certain conditions.std::forward
is a conditional cast. It casts to anrvalue
only if its argument was initialized with anrvalue
.
...
Given that bothstd::move
andstd::forward
boil down to casts, the only difference being thatstd::move
always casts, whilestd::forward
only sometimes does, you might ask whether we can dispense withstd::move
and just usestd::forward
everywhere. From a purely technical perspective, the answer is yes:std::forward
can do it all.std::move
isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky.
...std::move
’s attractions are convenience, reduced likelihood of error, and greater clarity...
For those interested, comparison of std::forward<T>
vs static_cast<T&&>
in assembly (without any optimization) when called with lvalue
and rvalue
.
If I want to change an lvalue to an rvalue, I have to usestd::move
, but notstd::forward
. Right?
– Yongwei Wu
Nov 12 at 10:23
Yes. move casts its argument to an rvalue.
– P.W
Nov 12 at 10:31
2
Sort of expected this comment from you. :-) I don't think that scott meyers meant that we can just replace move with forward. He gives an example in that item implementing move in terms of forward. " std::move requires only a function argument (rhs.s), while std::forward requires both a function argument (rhs.s) and a template type argument (std::string). Then note that the type we pass to std::forward should be a non-reference, because that’s the convention for encoding that the argument being passed is an rvalue (see Item 28). contd...
– P.W
Nov 12 at 10:43
3
...Together, this means that std::move requires less typing than std::forward, and it spares us the trouble of passing a type argument that encodes that the argument we’re passing is an rvalue. It also eliminates the possibility of our passing an incorrect type (e.g., std::string&, which would result in the data member s being copy constructed instead of move constructed). More importantly, the use of std::move conveys an unconditional cast to an rvalue, while the use of std::forward indicates a cast to an rvalue only for references to which rvalues have been bound.
– P.W
Nov 12 at 10:44
1
@M.M: Yes, looks like Scott Meyers did not take that into account. I was quoting him verbtim . :)
– P.W
Nov 12 at 11:50
|
show 3 more comments
up vote
12
down vote
Scott Meyers says that std::forward
and std::move
are mainly for convenience. He even states that std::forward
can be used to perform the functionality of both std::forward
and std::move
.
Some excerpts from "Effective Modern C++":
Item 23:Understand std::move and std::forward
...
The story forstd::forward
is similar to that forstd::move
, but whereasstd::move
unconditionally casts its argument to anrvalue
,std::forward
does it only under certain conditions.std::forward
is a conditional cast. It casts to anrvalue
only if its argument was initialized with anrvalue
.
...
Given that bothstd::move
andstd::forward
boil down to casts, the only difference being thatstd::move
always casts, whilestd::forward
only sometimes does, you might ask whether we can dispense withstd::move
and just usestd::forward
everywhere. From a purely technical perspective, the answer is yes:std::forward
can do it all.std::move
isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky.
...std::move
’s attractions are convenience, reduced likelihood of error, and greater clarity...
For those interested, comparison of std::forward<T>
vs static_cast<T&&>
in assembly (without any optimization) when called with lvalue
and rvalue
.
If I want to change an lvalue to an rvalue, I have to usestd::move
, but notstd::forward
. Right?
– Yongwei Wu
Nov 12 at 10:23
Yes. move casts its argument to an rvalue.
– P.W
Nov 12 at 10:31
2
Sort of expected this comment from you. :-) I don't think that scott meyers meant that we can just replace move with forward. He gives an example in that item implementing move in terms of forward. " std::move requires only a function argument (rhs.s), while std::forward requires both a function argument (rhs.s) and a template type argument (std::string). Then note that the type we pass to std::forward should be a non-reference, because that’s the convention for encoding that the argument being passed is an rvalue (see Item 28). contd...
– P.W
Nov 12 at 10:43
3
...Together, this means that std::move requires less typing than std::forward, and it spares us the trouble of passing a type argument that encodes that the argument we’re passing is an rvalue. It also eliminates the possibility of our passing an incorrect type (e.g., std::string&, which would result in the data member s being copy constructed instead of move constructed). More importantly, the use of std::move conveys an unconditional cast to an rvalue, while the use of std::forward indicates a cast to an rvalue only for references to which rvalues have been bound.
– P.W
Nov 12 at 10:44
1
@M.M: Yes, looks like Scott Meyers did not take that into account. I was quoting him verbtim . :)
– P.W
Nov 12 at 11:50
|
show 3 more comments
up vote
12
down vote
up vote
12
down vote
Scott Meyers says that std::forward
and std::move
are mainly for convenience. He even states that std::forward
can be used to perform the functionality of both std::forward
and std::move
.
Some excerpts from "Effective Modern C++":
Item 23:Understand std::move and std::forward
...
The story forstd::forward
is similar to that forstd::move
, but whereasstd::move
unconditionally casts its argument to anrvalue
,std::forward
does it only under certain conditions.std::forward
is a conditional cast. It casts to anrvalue
only if its argument was initialized with anrvalue
.
...
Given that bothstd::move
andstd::forward
boil down to casts, the only difference being thatstd::move
always casts, whilestd::forward
only sometimes does, you might ask whether we can dispense withstd::move
and just usestd::forward
everywhere. From a purely technical perspective, the answer is yes:std::forward
can do it all.std::move
isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky.
...std::move
’s attractions are convenience, reduced likelihood of error, and greater clarity...
For those interested, comparison of std::forward<T>
vs static_cast<T&&>
in assembly (without any optimization) when called with lvalue
and rvalue
.
Scott Meyers says that std::forward
and std::move
are mainly for convenience. He even states that std::forward
can be used to perform the functionality of both std::forward
and std::move
.
Some excerpts from "Effective Modern C++":
Item 23:Understand std::move and std::forward
...
The story forstd::forward
is similar to that forstd::move
, but whereasstd::move
unconditionally casts its argument to anrvalue
,std::forward
does it only under certain conditions.std::forward
is a conditional cast. It casts to anrvalue
only if its argument was initialized with anrvalue
.
...
Given that bothstd::move
andstd::forward
boil down to casts, the only difference being thatstd::move
always casts, whilestd::forward
only sometimes does, you might ask whether we can dispense withstd::move
and just usestd::forward
everywhere. From a purely technical perspective, the answer is yes:std::forward
can do it all.std::move
isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky.
...std::move
’s attractions are convenience, reduced likelihood of error, and greater clarity...
For those interested, comparison of std::forward<T>
vs static_cast<T&&>
in assembly (without any optimization) when called with lvalue
and rvalue
.
edited Nov 12 at 12:14
answered Nov 12 at 10:11
P.W
7,3642438
7,3642438
If I want to change an lvalue to an rvalue, I have to usestd::move
, but notstd::forward
. Right?
– Yongwei Wu
Nov 12 at 10:23
Yes. move casts its argument to an rvalue.
– P.W
Nov 12 at 10:31
2
Sort of expected this comment from you. :-) I don't think that scott meyers meant that we can just replace move with forward. He gives an example in that item implementing move in terms of forward. " std::move requires only a function argument (rhs.s), while std::forward requires both a function argument (rhs.s) and a template type argument (std::string). Then note that the type we pass to std::forward should be a non-reference, because that’s the convention for encoding that the argument being passed is an rvalue (see Item 28). contd...
– P.W
Nov 12 at 10:43
3
...Together, this means that std::move requires less typing than std::forward, and it spares us the trouble of passing a type argument that encodes that the argument we’re passing is an rvalue. It also eliminates the possibility of our passing an incorrect type (e.g., std::string&, which would result in the data member s being copy constructed instead of move constructed). More importantly, the use of std::move conveys an unconditional cast to an rvalue, while the use of std::forward indicates a cast to an rvalue only for references to which rvalues have been bound.
– P.W
Nov 12 at 10:44
1
@M.M: Yes, looks like Scott Meyers did not take that into account. I was quoting him verbtim . :)
– P.W
Nov 12 at 11:50
|
show 3 more comments
If I want to change an lvalue to an rvalue, I have to usestd::move
, but notstd::forward
. Right?
– Yongwei Wu
Nov 12 at 10:23
Yes. move casts its argument to an rvalue.
– P.W
Nov 12 at 10:31
2
Sort of expected this comment from you. :-) I don't think that scott meyers meant that we can just replace move with forward. He gives an example in that item implementing move in terms of forward. " std::move requires only a function argument (rhs.s), while std::forward requires both a function argument (rhs.s) and a template type argument (std::string). Then note that the type we pass to std::forward should be a non-reference, because that’s the convention for encoding that the argument being passed is an rvalue (see Item 28). contd...
– P.W
Nov 12 at 10:43
3
...Together, this means that std::move requires less typing than std::forward, and it spares us the trouble of passing a type argument that encodes that the argument we’re passing is an rvalue. It also eliminates the possibility of our passing an incorrect type (e.g., std::string&, which would result in the data member s being copy constructed instead of move constructed). More importantly, the use of std::move conveys an unconditional cast to an rvalue, while the use of std::forward indicates a cast to an rvalue only for references to which rvalues have been bound.
– P.W
Nov 12 at 10:44
1
@M.M: Yes, looks like Scott Meyers did not take that into account. I was quoting him verbtim . :)
– P.W
Nov 12 at 11:50
If I want to change an lvalue to an rvalue, I have to use
std::move
, but not std::forward
. Right?– Yongwei Wu
Nov 12 at 10:23
If I want to change an lvalue to an rvalue, I have to use
std::move
, but not std::forward
. Right?– Yongwei Wu
Nov 12 at 10:23
Yes. move casts its argument to an rvalue.
– P.W
Nov 12 at 10:31
Yes. move casts its argument to an rvalue.
– P.W
Nov 12 at 10:31
2
2
Sort of expected this comment from you. :-) I don't think that scott meyers meant that we can just replace move with forward. He gives an example in that item implementing move in terms of forward. " std::move requires only a function argument (rhs.s), while std::forward requires both a function argument (rhs.s) and a template type argument (std::string). Then note that the type we pass to std::forward should be a non-reference, because that’s the convention for encoding that the argument being passed is an rvalue (see Item 28). contd...
– P.W
Nov 12 at 10:43
Sort of expected this comment from you. :-) I don't think that scott meyers meant that we can just replace move with forward. He gives an example in that item implementing move in terms of forward. " std::move requires only a function argument (rhs.s), while std::forward requires both a function argument (rhs.s) and a template type argument (std::string). Then note that the type we pass to std::forward should be a non-reference, because that’s the convention for encoding that the argument being passed is an rvalue (see Item 28). contd...
– P.W
Nov 12 at 10:43
3
3
...Together, this means that std::move requires less typing than std::forward, and it spares us the trouble of passing a type argument that encodes that the argument we’re passing is an rvalue. It also eliminates the possibility of our passing an incorrect type (e.g., std::string&, which would result in the data member s being copy constructed instead of move constructed). More importantly, the use of std::move conveys an unconditional cast to an rvalue, while the use of std::forward indicates a cast to an rvalue only for references to which rvalues have been bound.
– P.W
Nov 12 at 10:44
...Together, this means that std::move requires less typing than std::forward, and it spares us the trouble of passing a type argument that encodes that the argument we’re passing is an rvalue. It also eliminates the possibility of our passing an incorrect type (e.g., std::string&, which would result in the data member s being copy constructed instead of move constructed). More importantly, the use of std::move conveys an unconditional cast to an rvalue, while the use of std::forward indicates a cast to an rvalue only for references to which rvalues have been bound.
– P.W
Nov 12 at 10:44
1
1
@M.M: Yes, looks like Scott Meyers did not take that into account. I was quoting him verbtim . :)
– P.W
Nov 12 at 11:50
@M.M: Yes, looks like Scott Meyers did not take that into account. I was quoting him verbtim . :)
– P.W
Nov 12 at 11:50
|
show 3 more comments
up vote
5
down vote
Here are my 2.5, not so technical cents for this: the fact that today std::forward
is indeed just a plain old static_cast<T&&>
does not mean that tomorrow it also will be implemented in exactly the same way. I think the committee needed something to reflect the desired behaviour of what std::forward
achieves today hence, the forward
which does not forward anything anywhere came into existence.
With having the required behaviour and expectations formalized under the umbrella of std::forward
, just theoretically speaking, noone impedes a future implementer to provide the std::forward
as not the static_cast<T&&>
but something specific his own implementation, without actually taking into consideration static_cast<T&&>
because the only fact that matters is the correct usage and behaviour of std::forward
.
I sincerely doubt thatforward
will ever do something else. Think of how much code that will break...
– Rakete1111
Nov 12 at 12:07
1
@Rakete1111 That is why my answer is just hypothetical, highly debatable and pure virtual :)
– fritzone
Nov 12 at 12:14
add a comment |
up vote
5
down vote
Here are my 2.5, not so technical cents for this: the fact that today std::forward
is indeed just a plain old static_cast<T&&>
does not mean that tomorrow it also will be implemented in exactly the same way. I think the committee needed something to reflect the desired behaviour of what std::forward
achieves today hence, the forward
which does not forward anything anywhere came into existence.
With having the required behaviour and expectations formalized under the umbrella of std::forward
, just theoretically speaking, noone impedes a future implementer to provide the std::forward
as not the static_cast<T&&>
but something specific his own implementation, without actually taking into consideration static_cast<T&&>
because the only fact that matters is the correct usage and behaviour of std::forward
.
I sincerely doubt thatforward
will ever do something else. Think of how much code that will break...
– Rakete1111
Nov 12 at 12:07
1
@Rakete1111 That is why my answer is just hypothetical, highly debatable and pure virtual :)
– fritzone
Nov 12 at 12:14
add a comment |
up vote
5
down vote
up vote
5
down vote
Here are my 2.5, not so technical cents for this: the fact that today std::forward
is indeed just a plain old static_cast<T&&>
does not mean that tomorrow it also will be implemented in exactly the same way. I think the committee needed something to reflect the desired behaviour of what std::forward
achieves today hence, the forward
which does not forward anything anywhere came into existence.
With having the required behaviour and expectations formalized under the umbrella of std::forward
, just theoretically speaking, noone impedes a future implementer to provide the std::forward
as not the static_cast<T&&>
but something specific his own implementation, without actually taking into consideration static_cast<T&&>
because the only fact that matters is the correct usage and behaviour of std::forward
.
Here are my 2.5, not so technical cents for this: the fact that today std::forward
is indeed just a plain old static_cast<T&&>
does not mean that tomorrow it also will be implemented in exactly the same way. I think the committee needed something to reflect the desired behaviour of what std::forward
achieves today hence, the forward
which does not forward anything anywhere came into existence.
With having the required behaviour and expectations formalized under the umbrella of std::forward
, just theoretically speaking, noone impedes a future implementer to provide the std::forward
as not the static_cast<T&&>
but something specific his own implementation, without actually taking into consideration static_cast<T&&>
because the only fact that matters is the correct usage and behaviour of std::forward
.
answered Nov 12 at 8:12
fritzone
19.8k1169117
19.8k1169117
I sincerely doubt thatforward
will ever do something else. Think of how much code that will break...
– Rakete1111
Nov 12 at 12:07
1
@Rakete1111 That is why my answer is just hypothetical, highly debatable and pure virtual :)
– fritzone
Nov 12 at 12:14
add a comment |
I sincerely doubt thatforward
will ever do something else. Think of how much code that will break...
– Rakete1111
Nov 12 at 12:07
1
@Rakete1111 That is why my answer is just hypothetical, highly debatable and pure virtual :)
– fritzone
Nov 12 at 12:14
I sincerely doubt that
forward
will ever do something else. Think of how much code that will break...– Rakete1111
Nov 12 at 12:07
I sincerely doubt that
forward
will ever do something else. Think of how much code that will break...– Rakete1111
Nov 12 at 12:07
1
1
@Rakete1111 That is why my answer is just hypothetical, highly debatable and pure virtual :)
– fritzone
Nov 12 at 12:14
@Rakete1111 That is why my answer is just hypothetical, highly debatable and pure virtual :)
– fritzone
Nov 12 at 12:14
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53257824%2fwhy-use-stdforwardt-instead-of-static-castt%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
I would think in some template library, people avoid using
std::forward
because they want the library be self-contained, and don't want to use standard library. They will also implement their ownstd::move
, etc.– liliscent
Nov 12 at 8:06
1
@VTT Not in this case, because lvalues are typically deduced as
T&
- wandbox.org/permlink/hPucHiFB2pwh53Ox– Curious
Nov 12 at 8:09
8
std::forward
andstd::move
are for readability, you could usestatic_cast
instead to get the same behaviour– M.M
Nov 12 at 8:12
What M.M said. In this answer, Howard mentions
std::move
was introduced to make the proposal more palatable. Then it stuck. I would imagine the same goes forstd::forward
.– StoryTeller
Nov 12 at 8:40
@M.M Your point about readability applies. However, std::move is slightly different in my mind because I don’t see the implementation going out of it’s way to prevent against user errors, making it a simpler 1-1 mapping of a cast. The fact that std::forward typically has two overloads, presumably for safety, makes me think that this situation is potentially more complex. Although, I don’t have a good reason to justify that. Hence, the question I guess.
– Curious
Nov 12 at 8:41