2017. std::reference_wrapper makes incorrect usage of std::result_of

Section: 23.14.5 [refwrap] Status: C++11 Submitter: Nikolay Ivchenkov Opened: 2010-11-15 Last modified: 2016-02-10

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 expect f(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 23.14.5.4 [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:

  1. Change the synopsis in 23.14.5 [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;
      };
    }
    
  2. Change the signature in 23.14.5.4 [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)