Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't `const int ci = 2; std::forward<int>(ci);` work and how to fix / workaround it?

Simple question, why doesn't the following work (implying a copy of ci)?

#include <utility>

int main(){
  const int ci = 2;
  std::forward<int>(ci);
}

prog.cpp: In function 'int main()':
prog.cpp:6:23: error: no matching function for call to 'forward(const int&)'

The problem manifested itself while writing some template stuff, where I have a simple holder type as follows. To avoid unnecessary copies, I use perfect forwarding where possible, but that turns out to be the root of the problem it seems.

template<class T>
struct holder{
    T value;

    holder(T&& val)
        : value(std::forward<T>(val))
    {}
};

template<class T>
holder<T> hold(T&& val){
    // T will be deduced as int, because literal `5` is a prvalue
    // which can be bound to `int&&`
    return holder<T>(std::forward<T>(val));
}

template<class T>
void foo(holder<T> const& h)
{
    std::tuple<T> t;  // contrived, actual function takes more parameters
    std::get<0>(t) = std::forward<T>(h.value); // h.value is `const T`
}

int main(){
    foo(hold(5));
}

If any further information is needed, please let me know.
Any idea to circumvent this problem is greatly appreciated.

like image 469
Xeo Avatar asked Nov 27 '11 04:11

Xeo


1 Answers

This:

#include <utility>

int main(){
  const int ci = 2;
  std::forward<int>(ci);
}

doesn't work because you can't implicitly cast away const. std::forward<T>(u) should be read as:

Forward u as a T.

You are attempting to say:

Forward an lvalue `const int` as an rvalue `int`.

which throws away the const. To avoid throwing away the const you could:

#include <utility>

int main(){
  const int ci = 2;
  std::forward<const int>(ci);
}

which says:

Forward an lvalue `const int` as an rvalue `const int`.

In your code:

template<class T>
void foo(holder<T> const& h)
{
    std::tuple<T> t;  // contrived, actual function takes more parameters
    std::get<0>(t) = std::forward<T>(h.value); // h.value is `const T`
}

the const qualifier on h impacts the data member selection expression h.value. h.value is a const lvalue int. You can use forward to change that into a const rvalue int, or you could use forward to pass it on unchanged (as a const lvalue int). You could even use forward to add volatile (though I can't think of a good reason to).

In your example, I'm seeing no reason to use forward at all (unless you take the const off of h).

    std::get<0>(t) = h.value; // h.value is `const T`

Your comment is even still correct.

It's a dry read, but N2951 surveys what you can and can not do with forward and why. This was modified by N3143 just prior to standardization, but the use cases and rationale from are still valid and unchanged in the final N3143 formulation.

Things you can do with forward:

  • You can forward an lvalue as an lvalue.
  • You can forward an lvalue as an rvalue.
  • You can forward an rvalue as an rvalue.
  • You can forward less cv-qualified expressions to more cv-qualified expressions.
  • You can forward expressions of derived type to an accessible, unambiguous base type.

Things you can not do with forward:

  • You can not forward an rvalue as an lvalue.
  • You can not forward more cv-qualified expressions to less cv-qualified expressions.
  • You can not forward arbitrary type conversions (e.g. forward an int as a double).
like image 156
Howard Hinnant Avatar answered Nov 15 '22 21:11

Howard Hinnant