const vector reference arguments in c++ - c++

I consider myself fairly knowledgeable regarding the more basic aspects of C++, but sometimes I tend to get confused. I'm trying to implement a pair of functions that act on instances of std::vector and do some operations with their values, without changing them in-place. Naturally, I thought that a pass by const& would be a reasonable thing to do. However, the following MWE fails to compile, and I would like to understand why:
#include<vector>
void func1(const std::vector<const char* const>& v){
// do stuff
}
void func2(const std::vector<const std::vector<const char* const> >& v){
for(int i=0;i<v.size();++i){
func1(v[i]);
}
}
int main(){
return 0;
}
The error message looks somewhat like this:
In file included from...include/c++/4.9.3/x86_64-unknown-linux-gnu/bits/c++allocator.h:33:0,
from...include/c++/4.9.3/bits/allocator.h:46,
from...include/c++/4.9.3/vector:61,
from test.cxx:2:
.../ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<const std::vector<const char* const> >’:
.../bits/allocator.h:92:11: required from ‘class std::allocator<const std::vector<const char* const> >’
.../ext/alloc_traits.h:172:53: required from ‘struct __gnu_cxx::__alloc_traits<std::allocator<const std::vector<const char* const> > >’
.../bits/stl_vector.h:75:28: required from ‘struct std::_Vector_base<const std::vector<const char* const>, std::allocator<const std::vector<const char* const> > >’
.../bits/stl_vector.h:214:11: required from ‘class std::vector<const std::vector<const char* const> >’
test.cxx:9:18: required from here
.../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::vector<const char* const>; __gnu_cxx::new_allocator<_Tp>::const_pointer = const std::vector<const char* const>*; __gnu_cxx::new_allocator<_Tp>::const_reference = const std::vector<const char* const>&]’ cannot be overloaded
address(const_reference __x) const _GLIBCXX_NOEXCEPT
^
.../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::vector<const char* const>; __gnu_cxx::new_allocator<_Tp>::pointer = const std::vector<const char* const>*; __gnu_cxx::new_allocator<_Tp>::reference = const std::vector<const char* const>&]’
address(reference __x) const _GLIBCXX_NOEXCEPT
^
.../ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<const char* const>’:
.../bits/allocator.h:92:11: required from ‘class std::allocator<const char* const>’
.../ext/alloc_traits.h:172:53: required from ‘struct __gnu_cxx::__alloc_traits<std::allocator<const char* const> >’
.../bits/stl_vector.h:75:28: required from ‘struct std::_Vector_base<const char* const, std::allocator<const char* const> >’
.../bits/stl_vector.h:214:11: required from ‘class std::vector<const char* const>’
test.cxx:10:14: required from here
.../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 char* const; __gnu_cxx::new_allocator<_Tp>::const_pointer = const char* const*; __gnu_cxx::new_allocator<_Tp>::const_reference = const char* const&]’ cannot be overloaded
address(const_reference __x) const _GLIBCXX_NOEXCEPT
^
.../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 char* const; __gnu_cxx::new_allocator<_Tp>::pointer = const char* const*; __gnu_cxx::new_allocator<_Tp>::reference = const char* const&]’
address(reference __x) const _GLIBCXX_NOEXCEPT
I'm confused by the fact that the error message claims that something cannot be overloaded, referring to the call of func1 inside func2. It seems like there's an issue with resolving the const reference to the vector entry, but I really don't understand why this piece of code does not compile. I'm happy for any explanation, suggestion, or pointer to documentation about this type of behavior.

std::vector<const char* const>
You are trying to instantiate a vector with a const type. The type needs to be at least moveable, and that isn't. Change to:
std::vector<const char*>
instead

The compiler reports its problem correctly, if you just know beforehand what that means.
The std::allocator has two overloads for address.
address(reference __x)
address(const_reference __x)
The problem for the compiler is that for a template type const T, the two types T& and const T& will be the same type. And then it believes that there are two copies of the same overload.

Related

Why is vector of const pointer working in c++17 [duplicate]

This question already has answers here:
What is the difference between const int*, const int * const, and int const *?
(23 answers)
Closed 3 years ago.
I saw this answer: Does C++11 allow vector<const T>? explains how you should not use const T in a std::vector. I tried this with std::vector<const int> int_vector and got compiler error, which is expected. But if I create an std::vector<const CustomType*> custom_type_vector I can use it with no problem. Does this mean c++ allows const pointer as an element within a std::vector but does not allow const T in std::vector?
minimal reproducible example
std::vector<const int> vec_a; // compiler will complain this.
std::vector<const int*> vec_a; // compiler will accept this.
The error log for using std::vector<const int> is:
/usr/include/c++/7/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 int; __gnu_cxx::new_allocator<_Tp>::const_pointer = const int*; __gnu_cxx::new_allocator<_Tp>::const_reference = const int&]' cannot be overloaded
address(const_reference __x) const _GLIBCXX_NOEXCEPT
^~~~~~~
/usr/include/c++/7/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 int; __gnu_cxx::new_allocator<_Tp>::pointer = const int*; __gnu_cxx::new_allocator<_Tp>::reference = const int&]'
address(reference __x) const _GLIBCXX_NOEXCEPT
^~~~~~~
/usr/include/c++/7/ext/new_allocator.h:125:19: error: invalid conversion from 'const void*' to 'void*' [-fpermissive]
::operator delete(__p);
~~~~~~~~~~~~~~~~~^~~~~
My compiler version is gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)
As m_pOatrix correctly points out in the comments, std::vector<const CustomType*> is not a vector of const pointers, but a vector of pointers to const objects, and this is allowed.
If you instead had const pointers, this would be disallowed:
std::vector<Foo> f; // allowed
std::vector<const Foo> f; // disallowed
std::vector<const Foo*> f;// allowed
std::vector<Foo* const> f; // disallowed
The trick with const is that it applies to the thing immediately to the left of it, or if it's at the beginning (like it often is) then it applies to the first thing.
const Foo*; // pointer to const Foo, same as Foo const*
Foo* const; // const pointer to Foo

std::set deduction guides don't work as I'd expect

I would expect deduction guides to correctly deduce the type in below example, yet they don't:
#include <set>
struct Foo { };
bool cmp(const Foo&, const Foo& );
std::set my_set({Foo{}, Foo{}}, cmp);
The compiler error (both gcc/clang show similar diagnostic):
In file included from /opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/x86_64-linux-gnu/bits/c++allocator.h:33,
from /opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/bits/allocator.h:46,
from /opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/bits/stl_tree.h:64,
from /opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/set:60,
from <source>:1:
/opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/ext/new_allocator.h: In instantiation of 'class __gnu_cxx::new_allocator<bool(const Foo&, const Foo&)>':
/opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/bits/alloc_traits.h:634:11: recursively required by substitution of 'template<class _Alloc> struct std::__is_allocator<_Alloc, std::__void_t<typename _Alloc::value_type, decltype (declval<_Alloc&>().allocate(long unsigned int{}))> > [with _Alloc = std::allocator<bool(const Foo&, const Foo&)>]'
/opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/bits/alloc_traits.h:634:11: required by substitution of 'template<class _Alloc> using _RequireAllocator = typename std::enable_if<std::__is_allocator<_Alloc>::value, _Alloc>::type [with _Alloc = std::allocator<bool(const Foo&, const Foo&)>]'
/opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/bits/stl_set.h:938:5: required by substitution of 'template<class _InputIterator, class _Compare, class _Allocator, class, class, class> std::set(_InputIterator, _InputIterator, _Compare, _Allocator)-> std::set<typename std::iterator_traits<_Iter>::value_type, _Compare, _Allocator> [with _InputIterator = bool (*)(const Foo&, const Foo&); _Compare = std::less<bool(const Foo&, const Foo&)>; _Allocator = std::allocator<bool(const Foo&, const Foo&)>; <template-parameter-1-4> = void; <template-parameter-1-5> = std::less<bool(const Foo&, const Foo&)>; <template-parameter-1-6> = <missing>]'
<source>:7:36: required from here
/opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/ext/new_allocator.h:96:7: error: 'const _Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::const_reference) const [with _Tp = bool(const Foo&, const Foo&); __gnu_cxx::new_allocator<_Tp>::const_pointer = bool (*)(const Foo&, const Foo&); __gnu_cxx::new_allocator<_Tp>::const_reference = bool (&)(const Foo&, const Foo&)]' cannot be overloaded with '_Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::reference) const [with _Tp = bool(const Foo&, const Foo&); __gnu_cxx::new_allocator<_Tp>::pointer = bool (*)(const Foo&, const Foo&); __gnu_cxx::new_allocator<_Tp>::reference = bool (&)(const Foo&, const Foo&)]'
96 | address(const_reference __x) const _GLIBCXX_NOEXCEPT
| ^~~~~~~
/opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/ext/new_allocator.h:92:7: note: previous declaration '_Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::reference) const [with _Tp = bool(const Foo&, const Foo&); __gnu_cxx::new_allocator<_Tp>::pointer = bool (*)(const Foo&, const Foo&); __gnu_cxx::new_allocator<_Tp>::reference = bool (&)(const Foo&, const Foo&)]'
92 | address(reference __x) const _GLIBCXX_NOEXCEPT
| ^~~~~~~
Compiler returned: 1
Which to me seems to hint at compiler not using the constructor taking the initializer list, but instead attempting to treat it as 2-iterator version (perhaps I am not reading it correctly, of course).
What might be the cause of this? Am I misreading deduction guides or is it something else?
It is probably a compiler/library bug, as little variant works:
Being explicit about initializer_list (Demo):
std::set my_set(std::initializer_list<Foo>{Foo{}, Foo{}}, cmp);
adding allocator (Demo):
std::set my_set({Foo{}, Foo{}}, cmp, std::allocator<Foo>{});
using Functor (Demo):
struct MyComparer
{
bool operator()(const Foo&, const Foo&) const;
};
std::set my_set({Foo{}, Foo{}}, MyComparer{});
And changing library (libc++ versus libstdc++) gives different results Demo for additional allocator variant.

const_iterator type of a container of const types

If I compile the code below:
#include <list>
using iter_t = std::list<const unsigned>::const_iterator;
int main(int, char**) {
return 0;
}
I get the following compile error:
In file included from /usr/local/Cellar/gcc/4.9.2_1/include/c++/4.9.2/x86_64-apple-darwin13.4.0/bits/c++allocator.h:33:0,
from /usr/local/Cellar/gcc/4.9.2_1/include/c++/4.9.2/bits/allocator.h:46,
from /usr/local/Cellar/gcc/4.9.2_1/include/c++/4.9.2/list:61,
from main.cpp:1:
/usr/local/Cellar/gcc/4.9.2_1/include/c++/4.9.2/ext/new_allocator.h: In instantiation of 'class __gnu_cxx::new_allocator<const unsigned int>':
/usr/local/Cellar/gcc/4.9.2_1/include/c++/4.9.2/bits/allocator.h:92:11: required from 'class std::allocator<const unsigned int>'
/usr/local/Cellar/gcc/4.9.2_1/include/c++/4.9.2/bits/stl_list.h:315:9: required from 'class std::_List_base<const unsigned int, std::allocator<const unsigned int> >'
/usr/local/Cellar/gcc/4.9.2_1/include/c++/4.9.2/bits/stl_list.h:447:11: required from 'class std::list<const unsigned int>'
main.cpp:3:41: required from here
/usr/local/Cellar/gcc/4.9.2_1/include/c++/4.9.2/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 unsigned int; __gnu_cxx::new_allocator<_Tp>::const_pointer = const unsigned int*; __gnu_cxx::new_allocator<_Tp>::const_reference = const unsigned int&]' cannot be overloaded
address(const_reference __x) const _GLIBCXX_NOEXCEPT
^
/usr/local/Cellar/gcc/4.9.2_1/include/c++/4.9.2/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 unsigned int; __gnu_cxx::new_allocator<_Tp>::pointer = const unsigned int*; __gnu_cxx::new_allocator<_Tp>::reference = const unsigned int&]'
address(reference __x) const _GLIBCXX_NOEXCEPT
^
I get roughly the same error message if I change the container type from std::list to std::vector, std::deque or std::forward_list. If I change the template argument from const unsigned to unsigned it compiles fine.
What's going on here? I can't see why it wouldn't compile.
Visual C++ 2015 gives the clarifying diagnostic
” The C++ Standard forbids containers of const elements because allocator<const T> is ill-formed.
As I understand it, this is because an allocator's allocate method returns a pointer to an array of uninitialized T items, and if T is const then these items can never be initialized.
I can't find any explicit statement that allocator<const T> is ill-formed, so it is presumably a consequence of the semantics.
As Dieter Lücking notes in a comment, there is a direct clash for const type T between the two member functions that provide address of a reference and const_reference, because these are equal when the type is const.

typename in dependent scope

Below is a condensed version of my code that gives me a compiler error. The compiler tells me to put typename in front of 'std::deque::reverse_iterator', which makes sense. But if I do I receive the error at the bottom. What does it mean? How can it be resolved?
#include <iostream>
#include <deque>
template<class T>
class Stack{
public:
Stack(){}
~Stack(){}
void push(T c) { s.push_back(c); }
void inspect() const{
for(typename std::deque<T>::reverse_iterator i=s.rbegin(); i!=s.rend(); i++)
std::cout << *i << std::endl;
}
private:
typename std::deque<T> s;
};
int main(){
Stack<int> s;
s.push(1);
s.inspect();
return 0;
}
Error:
error: no matching function for call to 'std::_Deque_iterator<int, int&, int*>::_Deque_iterator(std::reverse_iterator<std::_Deque_iterator<int, const int&, const int*> >::iterator_type)'|
note: candidates are:|
note: std::_Deque_iterator<_Tp, _Ref, _Ptr>::_Deque_iterator(const iterator&) [with _Tp = int; _Ref = int&; _Ptr = int*; std::_Deque_iterator<_Tp, _Ref, _Ptr>::iterator = std::_Deque_iterator<int, int&, int*>]|
note: no known conversion for argument 1 from 'std::reverse_iterator<std::_Deque_iterator<int, const int&, const int*> >::iterator_type {aka std::_Deque_iterator<int, const int&, const int*>}' to 'const iterator& {aka const std::_Deque_iterator<int, int&, int*>&}'|
note: std::_Deque_iterator<_Tp, _Ref, _Ptr>::_Deque_iterator() [with _Tp = int; _Ref = int&; _Ptr = int*]|
note: candidate expects 0 arguments, 1 provided|
note: std::_Deque_iterator<_Tp, _Ref, _Ptr>::_Deque_iterator(_Tp*, std::_Deque_iterator<_Tp, _Ref, _Ptr>::_Map_pointer) [with _Tp = int; _Ref = int&; _Ptr = int*; std::_Deque_iterator<_Tp, _Ref, _Ptr>::_Map_pointer = int**]|
note: candidate expects 2 arguments, 1 provided|
There's nothing dependent about std::deque<T>, so there mustn't be a typename. Only things to the right of a :: where the left depends on a template parameter is dependent.
This is a good example of where using auto would help you more than just saving typing. You're in a const member function, but trying to use your data member's reverse_iterator, not const_reverse_iterator.
Change typename std::deque<T>::reverse_iterator to typename std::deque<T>::const_reverse_iterator, or, more simply, auto.
This is in addition to the extra typename on your data member.

C++ Call pointer to member with a map from a const function

I have a map of pointer to member declared as :
std::map<char, T (Operand::*)(const T &, const T &)> op_map;
I fill my map with pointer to member directly in the constructor of my class with :
op_map['+'] = &Operand::op_add;
For example, op_add source code is :
T op_add(const T & a, const T & b) {
return a + b;
}
And I want to call my pointer to member from a const function. Here is the source code :
IOperand *res_int32(char op, const IOperand & rhs) const {
IOperand *res = const_cast<IOperand *>(&rhs);
Operand<int> *tmp = dynamic_cast<Operand<int>*>(res);
T res_calc = (this->*op_map[op])(_value, (T)tmp->getValue());
}
But it makes me always an error :
Operand.hpp:70:64: error: passing ‘const std::map<char, double (Operand<double>::*)(const double&, const double&), std::less<char>, std::allocator<std::pair<const char, double (Operand<double>::*)(const double&, const double&)> > >’ as ‘this’ argument of ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const key_type&) [with _Key = char, _Tp = double (Operand<double>::*)(const double&, const double&), _Compare = std::less<char>, _Alloc = std::allocator<std::pair<const char, double (Operand<double>::*)(const double&, const double&)> >, std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = double (Operand<double>::*)(const double&, const double&), std::map<_Key, _Tp, _Compare, _Alloc>::key_type = char]’ discards qualifiers [-fpermissive]
Operand.hpp:70:64: error: invalid conversion from ‘const Operand<double>* const’ to ‘Operand<double>*’ [-fpermissive]
Have you got any solution ?
Thank you.
operator[] can't be applied to a const map, since it inserts a new element if the key is not found.
In C++11, there is an at function which throws an exception if the key is not found:
T res_calc = (this->*op_map.at(op))(_value, (T)tmp->getValue());
^^^^^^^
In C++03, you'll need to use find:
map_type::const_iterator found = op_map.find(op);
if (found != op_map.end()) {
T res_calc = (this->*(found->second))(_value, (T)tmp->getValue());
} else {
// handle error
}
You'll also need to change the type of the member functions in the map to
T (Operand::*)(const T &, const T &) const
^^^^^
in order to call them on this from a const member function.
Just make op_add a const member function.
T op_add(const T & a, const T & b) const // <<<
{
return a + b;
}
And instead of the std::map::operator[] use std::map::find http://www.cplusplus.com/reference/stl/map/find/
EDIT:
You also need to change the map type to std::map<char, T (Operand::*)(const T &, const T &) const> op_map, as correctly pointed by R. Martinho Fernandes.
If you know what you are doing, you can try to compile with the c++ flag -fpermissive as G++ said.