Search code examples
c++c++11allocatorforwarding-reference

Allocator-related error when passing lvalue to a universal reference parameter that is used as template parameter to a temporary std container


I have stripped down my use case to a minimal meaningless example:

#include <vector>

template<typename Container>
void f(Container&) { // using && gives compilation error... (*)
    std::vector<Container>{};
};

int main() {
    std::vector<int> v;
    f(v); // (*) ... which is "solved" by std::move-ing v here. Why?
}

which compiles. However, when I add another & to make the unnamed parameter a forwarding reference, I get the error below (compiling with g++ -std=c++11 thatfile.cpp or -std=c++17). On the other hand, std::moveing v at the call site, solves the problem.

I'd like to understand why.

In file included from /usr/include/c++/10.2.0/x86_64-pc-linux-gnu/bits/c++allocator.h:33,
                 from /usr/include/c++/10.2.0/bits/allocator.h:46,
                 from /usr/include/c++/10.2.0/vector:64,
                 from prova.cpp:1:
/usr/include/c++/10.2.0/ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<std::vector<int>&>’:
/usr/include/c++/10.2.0/bits/allocator.h:116:11:   required from ‘class std::allocator<std::vector<int>&>’
/usr/include/c++/10.2.0/bits/stl_vector.h:87:21:   required from ‘struct std::_Vector_base<std::vector<int>&, std::allocator<std::vector<int>&> >’
/usr/include/c++/10.2.0/bits/stl_vector.h:389:11:   required from ‘class std::vector<std::vector<int>&, std::allocator<std::vector<int>&> >’
prova.cpp:5:5:   required from ‘void f(Container&&) [with Container = std::vector<int>&]’
prova.cpp:10:8:   required from here
/usr/include/c++/10.2.0/ext/new_allocator.h:62:26: error: forming pointer to reference type ‘std::vector<int>&’
   62 |       typedef _Tp*       pointer;
      |                          ^~~~~~~
/usr/include/c++/10.2.0/ext/new_allocator.h:63:26: error: forming pointer to reference type ‘std::vector<int>&’
   63 |       typedef const _Tp* const_pointer;
      |                          ^~~~~~~~~~~~~
/usr/include/c++/10.2.0/ext/new_allocator.h:103:7: error: forming pointer to reference type ‘std::vector<int>&’
  103 |       allocate(size_type __n, const void* = static_cast<const void*>(0))
      |       ^~~~~~~~
/usr/include/c++/10.2.0/ext/new_allocator.h:120:7: error: forming pointer to reference type ‘std::vector<int>&’
  120 |       deallocate(_Tp* __p, size_type __t)
      |       ^~~~~~~~~~

...

Solution

  • When you have

    template<typename Container>
    void f(Container&) { // using && gives compilation error... (*)
        std::vector<Container>{};
    };
    

    Constainer is deduced as a std::vector and all is well. When you use

    template<typename Container>
    void f(Container&&) { // using && gives compilation error... (*)
        std::vector<Container>{};
    };
    

    and you don't use std::move, then Container is deduced as std::vector&, which is a reference type, and you cannot create vectors of a reference type, so you get an error.

    When you use std::move(v), then you pass a std::vector&& to the function so Container again gets deduced to std::vector and the code compiles as it is not a reference type.