C++ Decay Copy

ISO/IEC JTC1 SC22 WG21 N3255 = 11-0025 - 2011-02-27

Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
Daniel Krügler, daniel.kruegler@googlemail.com

Introduction
Adding to the Standard
        20.3 Utility components [utility]
        20.3 forward/move helpers [forward]
        30.3.1.2 thread constructors [thread.thread.constr]
        30.4.4.2 Function call_once [thread.once.callonce]
Fully Postulating
        30.4.4.2 Function call_once [thread.once.callonce]

Introduction

Pablo Halpern noted a problem in meeting his intent when using rvalue references.

I was doing some experimentation and I wrote the following simple-looking function:


template <typename T>
void g(T&& v)
{
    std::pair<T,int> x(std::forward<T>(v), 99);
    f(x);
}

I tried calling the above this way:


double d = 5.6;
const double cd = 3.4;
g(d);
g(cd);
g(1.2);

and I (correctly) got 3 different types for the pair:


pair<double&, int>
pair<const double&, int>
pair<double, int>

The problem is that in most cases, what I really want is the last one. I could fix this two ways:

  1. eliminate the perfect forwarding:

    
    template <typename T>
    void g(const T& v)
    {
        std::pair<T,int> x(v, 99);
        f(x);
    }
    

    or

  2. use the following gawd-awful construction:

    
    template <typename T>
    void h(T&& v)
    {
        std::pair<
            typename std::remove_cv<
                typename std::remove_reference<T>::type
            >::type
            , int
        > x(v, 99);
        f(x);
    }
    

The disadvantage of both should be obvious. This leads me to three standard-related questions:

  1. Are there any recommended best methods for solving this cleanly?

  2. Should any of these best methods result in new components to the standard (perhaps in the next round)? E.g., should there be a remove_reference_and_cv metafunction?

  3. Should we add convenience-wrappers for things like traits classes to simplify this for users, e.g.

    
    template <class Iterator> struct iterator_traits<const Iterator>;
    template <class Iterator> struct iterator_traits<volatile Iterator>;
    template <class Iterator> struct iterator_traits<const volatile Iterator>;
    template <class Iterator> struct iterator_traits<Iterator&>;
    

Note that tuple_size and tuple_element already have specializations for cv-qualified tuple types, but for reference-to-tuple types. Is there some reason that doing the same for references would be a bad idea?

And the solution was to use the abstract function decay_copy.

Clark Nelson pointed out that the standard postulates decay_copy twice, but uses it three times.

Adding to the Standard

Given the demonstrated need for the decay_copy helper function, we should add it to the standard.

We decided not to add a noexcept specification because we have no compile-time trait for it now. What we would need is std::is_nothrow_convertible. The proper declaration would then be:


template <class T> typename decay<Tgt;::type
decay_copy(T&& v) noexcept(is_nothrow_convertible<T>::value);

20.3 Utility components [utility]

Edit the synopsis as follows.

Header <utility> synopsis

....

// 20.3.3, forward/move:
template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept;
template <class T> typename remove_reference<T>::type&& move(T&&) noexcept;
template <class T> typename conditional<
  !is_nothrow_move_constructible<T>::value && is_copy_constructible<T>::value,
  const T&, T&&>::type move_if_noexcept(T& x) noexcept;
template <class T> typename decay<T>::type decay_copy(T&& v);

....

20.3 forward/move helpers [forward]

Add a new function as follows.

template <class T> typename decay<T>::type decay_copy(T&& v);

Add a new paragraph as follows.

Effects: equivalent to return std::forward<T>(v).

Add a new paragraph as follows.

Remarks: This function shall not participate in overload resolution unless T is implicitly convertible to decay<T>::type.

Add a new paragraph as follows.

[[Note: See 30.3.1.2 [thread.thread.constr], 30.4.4.2 [thread.once.callonce], and 30.6.9 [futures.async] for examples of use. —end note]

30.3.1.2 thread constructors [thread.thread.constr]

Edit the following thread constructor as follows.

template <class F, class ...Args> explicit thread(F&& f, Args&&... args);

Given a function as follows:


template <class T> typename decay<T>::type decay_copy(T&& v)
  { return std::forward<T>(v); }

30.4.4.2 Function call_once [thread.once.callonce]

Edit function call_once as follows.

template<class Callable, class ...Args>
  void call_once(once_flag& flag, Callable&& func, Args&&... args);

Given a function as follows:


template <class T> typename decay<T>::type decay_copy(T&& v)
  { return std::forward<T>(v); }

Fully Postulating

If the above solution proves unacceptable, at the very least, we should postulate it for all uses.

30.4.4.2 Function call_once [thread.once.callonce]

Edit function async as follows.

template <class F, class... Args>
  future<typename result_of<F(Args...)>::type>
  async(F&& f, Args&&... args);
template <class F, class... Args>
  future<typename result_of<F(Args...)>::type>
  async(launch policy, F&& f, Args&&... args);

Given a function as follows:


template <class T> typename decay<T>::type decay_copy(T&& v)
  { return std::forward<T>(v); }