Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store either rvalue or lvalue references in template

Tags:

c++

c++11

rvalue

I am trying to create a simple template enumerator class that should accept any object over which : operator is defined and later on print pairs of the form (i, v[i]). A simple implementation is the following:

template<typename T>
struct enumerator {
    T &v; // reference to minimize copying
    enumerator(T &_v) : v(_v) {}
    void do_enumerate() {
        size_t i = 0;
        for(auto x : v) {
            cout << i << x << endl;
            i++;
        }
    }
};

This works ok for things like:

Case A

vector<int> v({1,2,6,2,4});
auto e = enumerator(v);
e.do_enumerate();

However, I would also like it to handle temporary objects like:

Case B

auto e = enumerator(vector<int>({2,3,9});
e.do_enumerate();

This does not work and the compiler throws:

no matching function for call to ‘enumerator<std::vector<int> >::enumerator(std::vector<int>)

So, I tried to then add a

enumerator(T _t) : t(_T) {}

constructor to resolve this error. Now case A does not work and there is an error:

error: call of overloaded ‘enumerator(std::vector<int>&)’ is ambiguous

Moreover, in case B, the output of the enumeration is not correct.

What is the cleanest way to solve this? I would

  • really like both cases to be working
  • prefer not to use any libraries other than stdc++
  • want as little copying as possible (thus just storing a T t in the struct is not an option)
  • C++11 is not a problem. I have g++-4.8, which I presume has complete enough C++11 support.
like image 831
Subhasis Das Avatar asked Jun 12 '14 01:06

Subhasis Das


People also ask

Can you pass an lvalue to an rvalue reference?

In the example, the main function passes an rvalue to f . The body of f treats its named parameter as an lvalue. The call from f to g binds the parameter to an lvalue reference (the first overloaded version of g ). You can cast an lvalue to an rvalue reference.

What is the difference between L value and R value references?

“l-value” refers to a memory location that identifies an object. “r-value” refers to the data value that is stored at some address in memory. References in C++ are nothing but the alternative to the already existing variable. They are declared using the '&' before the name of the variable.

How do I create an rvalue reference?

An lvalue reference is formed by placing an & after some type. An rvalue reference is formed by placing an && after some type. An rvalue reference behaves just like an lvalue reference except that it can bind to a temporary (an rvalue), whereas you can not bind a (non const) lvalue reference to an rvalue.

Can rvalue bind to lvalue?

In this example, the rvalue reference a can be bound to the temporary initialized with the rvalue expression 2 , but the rvalue reference b cannot be bound to the lvalue expression i . You can bind the rvalue reference c to the temporary value 1.0 that is converted from the variable i . End of C++11 only.


2 Answers

Well I would like to copy in case the argument is an rvalue, and not copy in case it is not. Is that possible?

This can be accomplished using a make_enumerator helper function as shown.

template <class T>
struct enumerator {
    T v;
    enumerator(T&& _v) : v(std::forward<T>(_v)) {}
    void do_enumerate() {
        size_t i = 0;
        for(auto x : v) {
            cout << i << x << endl;
            i++;
        }
    }
};

template <class T>
enumerator<T> make_enumerator(T&& x) {
    return enumerator<T>(std::forward<T>(x));
}

int main() {
    vector<int> v {5, 2, 9, 1};
    make_enumerator(v).do_enumerate();
    make_enumerator(std::move(v)).do_enumerate();
}

How does this work?

If the argument to make_enumerator is an lvalue of type A then T is deduced as A& and we get the enumerator enumerator<A&>, whereas if it is an rvalue of type A then T is deduced as A and we get the enumerator enumerator<A>.

In the first case the member enumerator::v will have type A&, an lvalue reference that binds to the constructor argument (no copying). In the second case the member will have type A. The use of std::forward casts the parameter _v to an rvalue, so it will be moved from when it is used to initialize v.

like image 195
Brian Bi Avatar answered Oct 04 '22 22:10

Brian Bi


This is a classic example where you don't actually need a class/struct (which actually introduce useless code) and you can just use good old functions:

template<typename Container>
void enumerate(const Container& t) {
    std::size_t i = 0;
    for(auto it = t.begin(); it != t.end(); ++it, ++i)
        std::cout << i << *it << std::endl;
}

and then call it as:

enumerate(std::vector<int>{2,3,9});

Live demo

With this method you also get argument type inference for free (which you don't get with a struct).

like image 31
Shoe Avatar answered Oct 04 '22 20:10

Shoe