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
T t
in the struct is not an option)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.
“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.
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.
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.
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
.
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
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With