jaredjxyz
jaredjxyz

Reputation: 73

Why is std::forward turning my lvalue into an rvalue?

template<class T>
struct IntHolder {
    T i;
};

template<class T>
void addOne(T& t) {
    t.i += 1;
}

template<class... Args>
void addAll(Args... args) {
      // Magic to run addOne on each arg
      int dummy[] = { 0, ((void)addOne(std::forward<Args>(args)), 0)... };
}

int main() {
    IntHolder<int> x{2};
    IntHolder<double> t{3};
    addAll(x, t);
    return 0;
}

This toy example won't compile because

prog.cc: In instantiation of 'void addAll(Args ...) [with Args = {IntHolder<int>, IntHolder<double>}]':
prog.cc:60:16:   required from here
prog.cc:54:39: error: invalid initialization of non-const reference of type 'IntHolder<int>&' from an rvalue of type 'IntHolder<int>'
       int dummy[] = { 0, ((void)addOne(std::forward<Args>(args)), 0)... };
                                       ^
prog.cc:48:6: note:   initializing argument 1 of 'void addOne(T&) [with T = IntHolder<int>]'
 void addOne(T& t) {

What I thought would happen here is that addAll gets two lvalues passed in, and then addOne would be called on each of those as an lvalue. However, it seems somewhere along the way, the compiler thinks that the argument is getting converted to an rvalue. Why might this be?

Upvotes: 2

Views: 61

Answers (1)

songyuanyao
songyuanyao

Reputation: 172924

You're declaring parameter as non-reference, i.e. pass-by-value, then when being passed x and t, the type would be deduced as IntHolder<int> and IntHolder<double>. Then std::forward<Args> would convert them into rvalues.

For forwarding reference it should be

template<class... Args>
void addAll(Args&&... args) {
    //          ^^
    // Args would be deduced as IntHolder<int>&, IntHolder<double>&
    // then std::forward<Args>(args) yields lvalues 
    int dummy[] = { 0, ((void)addOne(std::forward<Args>(args)), 0)... };
}

Upvotes: 6

Related Questions