18

Suppose I have two structs:

struct X {};
struct Y { X x; }

I have functions:

void f(X&);
void f(X&&);

How do I write a function g() that takes Y& or Y&& but perfect forwarding X& or X&& to f(), respectively:

template <typename T>
void g(T&& t) {
  if (is_lvalue_reference<T>::value) {
    f(t.x);
  } else {
    f(move(t.x));
  }
}

The above code illustrate my intention but is not very scalable as the number of parameters grows. Is there a way make it work for perfect forwarding and make it scalable?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Kan Li
  • 8,557
  • 8
  • 53
  • 93
  • I think changing `is_lvalue_reference::value` to `is_lvalue_reference(t))>::value` will have the semantics you want, but I think your desired semantics are questionable... – ildjarn Dec 20 '11 at 04:08
  • (Sorry for the botched answer.) I'd say the reason that it doesn't scale is because the design is questionable to begin with. What does it mean to "move" a subobject? In what state does this leave the main object? Even if there were an easy way to write this, it looks like poorly structured code... – Kerrek SB Dec 20 '11 at 04:12

2 Answers2

26
template <typename T>
void g(T&& t) {
  f(std::forward<T>(t).x);
}
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • @Pubby because `rvalue.foo` is an rvalue. I can think of why it may make sense (if the container has a short lifetime and is an rvalue, the contained object too shares that property), but I'm not familiar with the philosophies and don't know the rationale, so I can't say anything about it. – Johannes Schaub - litb Dec 22 '11 at 20:58
  • `rvalue.foo` should be a xvalue actauly. – curiousguy Dec 23 '11 at 02:26
  • @curi some committe member said so. But then what would he the reason for "rvalue" (the object expression) sometimes staying a prvalue? Against making the member selection an xvalue is the fact that its dynamic type cannot diffe from the static type of the expression. – Johannes Schaub - litb Dec 23 '11 at 08:35
  • 1
    This is exactly the answer I am looking for. – Kan Li Dec 29 '11 at 21:36
  • What a genius!! – user5280911 Apr 08 '20 at 06:15
3

I think this will work, although I'm not sure:

template<class T, class M>
struct mforward {
  using type = M&&; 
};
template<class T, class M>
struct mforward<T&, M> {
  using type = M&; 
};

template <typename T>
void g(T&& t) {
  f(std::forward<typename mforward<T, decltype(t.x)>::type>(t.x));
}
Pubby
  • 51,882
  • 13
  • 139
  • 180
  • 2
    +1 I had to solve nearly this exact problem in libc++. The solution I came up with looked very similar to Pubby's. Not only did I want to "apply" the l/r-valueness of `T` to `M`, I also wanted to apply the cv-qualifications of `T` to `M`. And I found applications for it beyond data members. For the curious, I called it `__apply_cv` and it is open source code at: http://libcxx.llvm.org/ – Howard Hinnant Dec 20 '11 at 14:25
  • @Howard see my answer for an alternative. Or do I miss something? – Johannes Schaub - litb Dec 22 '11 at 20:15
  • Also @Howard talking about cv qualification makes me think about a flaw of this answer. `decltype(t.x)` only gives you the declared type of `x`. If `T` is `const`, but `x` was declared as `int a` then your forward will try to forward things as `int&` / `int&&`. You would need to say something like `typename remove_reference::type` to take into account the constness of `t` too. This also takes into account `mutable` members, but I don't know what move means philosophically to a mutable member of an otherwise `const` object. – Johannes Schaub - litb Dec 22 '11 at 20:30
  • @Johannes: I agree, your solution looks better for this use case. – Howard Hinnant Dec 22 '11 at 23:02
  • @Howard: What would a case look like where Johannes' solution would not work, but the `__apply_cv` one would? – Xeo Dec 23 '11 at 04:30
  • @Xeo: I used `__apply_cv` in computing the traits `make_signed` and `make_unsigned`, though in this application I'm only transferring cv-qualification and not l/rvalueness. I also used it both in `result_of` and in `tuple_cat`. But to be honest, these latter two uses are in such complicated code that I will stop well short of claiming that these implementations are optimal as they stand. I would have to put an hour into studying these to be sure, and I've got to move on for now... :-) – Howard Hinnant Dec 23 '11 at 15:19