Why does const vector<const pair<...>> give an 'cannot be overloaded' error?

Viewed 1220

I have this simple code:

#include <vector>
#include <string>

void foo(const std::vector<std::pair<std::string, int> > & networks) {
  for (auto p : networks) {
  }
}

void bla(const std::vector<const std::pair<std::string, int> > & networks) {
  for (auto p : networks) {
  }
}

This produces an error in bla():

mrvn@frosties:~% g++ -O2 -W -Wall -g -std=gnu++17 -c bla.cc
In file included from /usr/include/x86_64-linux-gnu/c++/5/bits/c++allocator.h:33:0,
                 from /usr/include/c++/5/bits/allocator.h:46,
                 from /usr/include/c++/5/vector:61,
                 from bla.cc:1:
/usr/include/c++/5/ext/new_allocator.h: In instantiation of ‘struct __gnu_cxx::new_allocator<const std::pair<std::__cxx11::basic_string<char>, int> >’:
/usr/include/c++/5/bits/allocator.h:92:11:   required from ‘class std::allocator<const std::pair<std::__cxx11::basic_string<char>, int> >’
/usr/include/c++/5/bits/stl_vector.h:79:14:   required from ‘struct std::_Vector_base<const std::pair<std::__cxx11::basic_string<char>, int>, std::allocator<const std::pair<std::__cxx11::basic_string<char>, int> > >::_Vector_impl’
/usr/include/c++/5/bits/stl_vector.h:164:20:   required from ‘struct std::_Vector_base<const std::pair<std::__cxx11::basic_string<char>, int>, std::allocator<const std::pair<std::__cxx11::basic_string<char>, int> > >’
/usr/include/c++/5/bits/stl_vector.h:214:11:   required from ‘class std::vector<const std::pair<std::__cxx11::basic_string<char>, int> >’
bla.cc:10:17:   required from here
/usr/include/c++/5/ext/new_allocator.h:93:7: error: ‘const _Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::const_reference) const [with _Tp = const std::pair<std::__cxx11::basic_string<char>, int>; __gnu_cxx::new_allocator<_Tp>::const_pointer = const std::pair<std::__cxx11::basic_string<char>, int>*; __gnu_cxx::new_allocator<_Tp>::const_reference = const std::pair<std::__cxx11::basic_string<char>, int>&]’ cannot be overloaded
       address(const_reference __x) const _GLIBCXX_NOEXCEPT
       ^
/usr/include/c++/5/ext/new_allocator.h:89:7: error: with ‘_Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::reference) const [with _Tp = const std::pair<std::__cxx11::basic_string<char>, int>; __gnu_cxx::new_allocator<_Tp>::pointer = const std::pair<std::__cxx11::basic_string<char>, int>*; __gnu_cxx::new_allocator<_Tp>::reference = const std::pair<std::__cxx11::basic_string<char>, int>&]’
       address(reference __x) const _GLIBCXX_NOEXCEPT
       ^

My Question is: WHY?

Note: used g++ 5.4 and 7.3.

2 Answers

This is what I could gather so far from the standard and documentation:

std::vector is an allocator-aware container.

As per C++17 (final working draft N4659)

20.5.3.5 Allocator requirements [allocator.requirements]

Table 30 says:

T, U, C any cv-unqualified object type (6.9)

For std::vector it is also required that element type is a complete type and meets the requirements of Erasable.

From [container.requirements.general]/15 we have:

Given an allocator type A and given a container type X having a value_type identical to T and an allocator_- type identical to allocator_traits<A>::rebind_alloc<T> and given an lvalue m of type A, a pointer p of type T*, an expression v of type (possibly const) T, and an rvalue rv of type T, the following terms are defined.
...
(15.6) — T is Erasable from X means that the following expression is well-formed: allocator_traits<A>::destroy(m, p)

Since element type in the question is const qualified, it fails to meet the requirements.

There is a requirement for having non const values in a vector:

/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/stl_vector.h:351:7: error: static_assert failed due to requirement 'is_same, allocator >, int> >::type, const pair, allocator >, int> >::value' "std::vector must have a non-const, non-volatile value_type"

Let's start from the vector requirements down to the allocator issue. The type in vector must support this:

The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type meets the requirements of Erasable, but many member functions impose stricter requirements. This container (but not its members) can be instantiated with an incomplete element type if the allocator satisfies the allocator completeness requirements.

So we need to have something that can be Erasable and where we can do:

std::allocator_traits<A>::destroy(m, p);

with m the allocator and p the type that we want to destroy. Fair enough. What more do we need? Erasable needs an Allocator:

Specifies that an object of the type can be destroyed by a given Allocator.

, which in turns requires T to be cv-unqualified:

T, a cv-unqualified object type

So you can have a std::pair<const string, int>, but not a const std::pair<std::string, int>.

In that regard, MSVC message is clearer, as it directly tells you that it's the allocator that is ill-formed.

Related