std::reference_wrapper
makes incorrect usage of std::result_of
Section: 22.10.6 [refwrap] Status: C++11 Submitter: Nikolay Ivchenkov Opened: 2010-11-15 Last modified: 2016-01-28
Priority: Not Prioritized
View all other issues in [refwrap].
View all issues with C++11 status.
Discussion:
std::reference_wrapper
's function call operator uses wrong
type encoding for rvalue-arguments. An rvalue-argument of type T
must
be encoded as T&&
, not as just T
.
#include <functional> #include <iostream> #include <string> #include <type_traits> #include <utility> template <class F, class... Types> typename std::result_of<F (Types...)>::type f1(F f, Types&&... params) { return f(std::forward<Types...>(params...)); } template <class F, class... Types> typename std::result_of<F (Types&&...)>::type f2(F f, Types&&... params) { return f(std::forward<Types...>(params...)); } struct Functor { template <class T> T&& operator()(T&& t) const { return static_cast<T&&>(t); } }; int main() { typedef std::string const Str; std::cout << f1(Functor(), Str("1")) << std::endl; // (1) std::cout << f2(Functor(), Str("2")) << std::endl; // (2) }
Lets consider the function template f1
(which is similar to
std::reference_wrapper
's function call operator). In the invocation
(1) F
is deduced as 'Functor
' and Types
is deduced as type sequence
which consists of one type 'std::string const
'. After the substitution
we have the following equivalent:
template <> std::result_of<F (std::string const)>::type f1<Functor, std::string const>(Functor f, std::string const && params) { return f(std::forward<const std::string>(params)); }
The top-level cv-qualifier in the parameter type of 'F (std::string const)
' is removed, so we have
template <> std::result_of<F (std::string)>::type f1<Functor, std::string const>(Functor f, std::string const && params) { return f(std::forward<const std::string>(params)); }
Let r
be an rvalue of type 'std::string
' and cr
be an rvalue of type
'std::string const
'. The expression Str("1")
is cr
. The corresponding
return type for the invocation
Functor().operator()(r)
is 'std::string &&
'. The corresponding return type for the invocation
Functor().operator()(cr)
is 'std::string const &&
'.
std::result_of<Functor (std::string)>::type
is the same type as the
corresponding return type for the invocation Functor().operator()(r)
,
i.e. it is 'std::string &&
'. As a consequence, we have wrong reference
binding in the return statement in f1
.
Now lets consider the invocation (2) of the function template f2
. When
the template arguments are substituted we have the following equivalent:
template <> std::result_of<F (std::string const &&)>::type f2<Functor, std::string const>(Functor f, std::string const && params) { return f(std::forward<const std::string>(params)); }
std::result_of<F (std::string const &&)>::type
is the same type as
'std::string const &&
'. This is correct result.
[ 2010-12-07 Jonathan Wakely comments and suggests a proposed resolution ]
I agree with the analysis and I think this is a defect in the standard, it would be a shame if it can't be fixed.
In the following example one would expectf(Str("1"))
and
std::ref(f)(Str("2"))
to be equivalent but the current wording makes
the invocation through reference_wrapper
ill-formed:
#include <functional> #include <string> struct Functor { template <class T> T&& operator()(T&& t) const { return static_cast<T&&>(t); } }; int main() { typedef std::string const Str; Functor f; f( Str("1") ); std::ref(f)( Str("2") ); // error }
[ 2010-12-07 Daniel comments and refines the proposed resolution ]
There is one further defect in the usage of result_of
within
reference_wrapper
's function call operator: According to 22.10.6.5 [refwrap.invoke] p. 1
the invokable entity of type T
is provided as lvalue, but
result_of
is fed as if it were an rvalue. This does not only lead to
potentially incorrect result types, but it will also have the effect that
we could never use the function call operator with a function type,
because the type encoding used in result_of
would form an invalid
function type return a function type. The following program demonstrates
this problem:
#include <functional> void foo(int) {} int main() { std::ref(foo)(0); // error }
The correct solution is to ensure that T
becomes T&
within result_of
, which solves both problems at once.
[2011-02-24 Reflector discussion]
Moved to Tentatively Ready after 5 votes.
Proposed resolution:
Change the synopsis in 22.10.6 [refwrap] paragraph 1:
namespace std { template <class T> class reference_wrapper { public : [...] // invocation template <class... ArgTypes> typename result_of<T&(ArgTypes&&...)>::type operator() (ArgTypes&&...) const; }; }
Change the signature in 22.10.6.5 [refwrap.invoke] before paragraph 1
template <class... ArgTypes> typename result_of<T&(ArgTypes&&... )>::type operator()(ArgTypes&&... args) const;1 Returns:
INVOKE(get(), std::forward<ArgTypes>(args)...)
. (20.8.2)