9

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.

Xeo
  • 123,374
  • 44
  • 277
  • 381
  • You should do this: `template struct holder { holder(T val) : value(std::move(val)){} }; template holder::type> hold(T val) { return holder(std::move(val)); }` (Yes I'm aware that looks jumbly. :)) – GManNickG Nov 27 '11 at 07:03
  • @GMan: But I don't want to remove the reference. :( – Xeo Nov 27 '11 at 07:04
  • 1
    Then you call it as `hold(std::ref(x));`. :P – GManNickG Nov 27 '11 at 07:06
  • @GMan: Eww. ;/ Seems I need to explain what I'm trying to do. I'm currently trying to implement positional parameters, like `void foo(param_pack p){...} /*...*/ foo((_2 = some_var, _1 = 1));`. Contrived example, but should bring my intent across. Sadly, I'm running into problem after problem with this. :( Now it's the references, which break when you try to move them: http://ideone.com/Vx0Jp – Xeo Nov 27 '11 at 07:13
  • Oh, wait. I just got a fine idea to work around that problem~ Partial specializations ftw. – Xeo Nov 27 '11 at 07:37
  • I was going to suggest that, but I've tried to avoid specializations as of late, if possible. (But not religiously, so I'd say go for it.) – GManNickG Nov 27 '11 at 07:41
  • @GMan: Why are you trying to avoid specializations? And *special* reason? :) – Xeo Nov 27 '11 at 07:42
  • I've just found in the past when I'm writing generic code and I say, "it's not working with this type...specialize!" it ends up biting me because it wasn't the *type* in particular that was the problem, but the interaction with an entire set of types satisfying some concept. So if I'm having a problem like yours, it would be better to capture the concept that's the issue, not the type (or type class) in particular. But sometimes (perhaps often times) it really is just a fundamental type that needs special handing. – GManNickG Nov 27 '11 at 07:45

1 Answers1

20

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).
Howard Hinnant
  • 179,402
  • 46
  • 391
  • 527
  • Thanks for this extensive answer. Is there any way to "save" the forwardability of a value? Like I said in my comment on the question itself, I'm trying to implement positional parameters, and I'd like to forward all parameters like they were directly passed into the function. [Here](http://ideone.com/jeprD) is the full code I currently have (~300 LoC), even though GCC doesn't seem to like my SFINAE trickery on `switch_`. – Xeo Nov 27 '11 at 19:09
  • I already found a few limitations in my system, such as that I can't bind temporaries of type `T` to `T const&` or `T&&` in the `param_pack` without getting dangling references. Is there any way to fix / improve that (if you're willing to wade through the code)? – Xeo Nov 27 '11 at 19:10
  • @Xeo: I'm unsure. But I was able to fix your code up such that it runs on clang/libc++. `std::move(pos::template switch_<0>(arg1, arg2, arg3).value` (add "template" 3 places). Make param_pack BoundList constructor less generic: `template param_pack(bound_list&& bl)`. With those changes your code runs for me and outputs: `Value ctor Move ctor Move ctor Move ctor Copy ctor Move ctor Move ctor hi 3 1 Dtor Dtor` – Howard Hinnant Nov 27 '11 at 19:59
  • D'oh, the dreaded nested template specifier! Thanks for the fix. Actually, the code I linked wasn't the most up-to-date, but I noticed that too late. I already made the change to the ctor, as the `bound_list` will only ever be a temporary anyways. The output is the same for me on VS11, but I'd like to get that one copy out. Sadly, step-into doesn't really help for VS11, as the relevant function where it happens is deeply burried inside a macro to simulate variadic templates. :( For anyone reading this and being interested, [here](http://ideone.com/iQcDW) is the fixed code for GCC. – Xeo Nov 27 '11 at 20:09