Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Universal Reference of vector

Tags:

c++

I would like to use a universal reference with vector.

template<typename T>
    void foo(T&& v)
    {
        for(typename T::iterator i = v.begin(); i != v.end(); i++)
        {
            std::cout << *i << std::endl;
        }
    }

int main()
{
    std::vector v = {0,5,4,3};
    foo(std::move(v));
    foo(v); //compiler error
    return 0;
}

but when I use as argument of foo function v(without std::move) it cause a compiler error.

I think that in both case universal reference should work.

Error:

prog.cc: In instantiation of 'void foo(T&&) [with T = std::vector<int, std::allocator<int> >&]':
prog.cc:25:10: required from here prog.cc:16:30: error: 'std::vector<int, std::allocator<int> >&' is not a class, struct, or union type 16 | for(typename T::iterator i = v.begin(); i != v.end(); i++) | ^
prog.cc:16:30: error: 'std::vector<int, std::allocator<int> >&' is not a class, struct, or union type 
like image 571
KeyB0rys Avatar asked Dec 05 '22 11:12

KeyB0rys


2 Answers

When you pass an lvalue to foo, T is deduced as an lvalue reference due to special perfect forwarding rules:

From [temp.deduct.call] (12.9.2.1 par 3):

A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

Therefore typename T::iterator will attempt to access the iterator type alias of an lvalue reference, which does not exist.

You can either change it to:

typename std::remove_reference_t<T>::iterator

Or simply:

for(auto i = v.begin(); i != v.end(); i++) { /* ... */ }

Or even better:

for(const auto& x : v) { std::cout << x << '\n'; }
like image 140
Vittorio Romeo Avatar answered Dec 28 '22 19:12

Vittorio Romeo


The issue is the explicit loop:

for(typename T::iterator i = v.begin(); i != v.end(); i++)

where T is replaced by type the template is instantiated with. For an rvalue reference, this is T (and the function argument is an rvalue reference), for an lvalue-reference, it is T& (and due to reference collapsing, the function argument is an lvalue reference). The line above hence tries something like typename T&::iterator i which does not work. One way to fix this is e.g.

for(typename std::decay_t<T>::iterator i = v.begin(); i != v.end(); i++)

But the function template is also a nice demonstration of the declarative power of range based for loops, as this works for both cases without any manual removal of references:

for (auto& i : v)
   std::cout << i << "\n";
like image 44
lubgr Avatar answered Dec 28 '22 20:12

lubgr