1

I'm coming back to C++ after many years and I'm on the C++17 standard. As per this question, it seems custom structs with const members are not always compatible with vectors without public copy-assignment constructors.

In my case though, unlike the linked question, the following compiles fine:

#include <vector>

struct Foo { const int m_Bar; };

int main()
{
    std::vector<Foo> vecFoos{ Foo{1} };
    vecFoos.push_back({ Foo{2} });
    return 0;
}

And it's the following that doesn't:

#include <vector>

struct Foo { const int m_Bar; };

int main()
{
    std::vector<Foo> vecFoos{ Foo{1} };
    vecFoos.assign({ Foo{2} });  // using assign instead of push_back
    return 0;
}

It fails with:

$ g++ --std=c++17 main.cpp  && ./a.out 
In file included from /usr/include/c++/9/vector:60,
                 from main.cpp:1:
/usr/include/c++/9/bits/stl_algobase.h: In instantiation of ‘static _Tp* std::__copy_move<_IsMove, true, std::random_access_iterator_tag>::__copy_m(const _Tp*, const _Tp*, _Tp*) [with _Tp = Foo; bool _IsMove = false]’:
/usr/include/c++/9/bits/stl_algobase.h:404:30:   required from ‘_OI std::__copy_move_a(_II, _II, _OI) [with bool _IsMove = false; _II = const Foo*; _OI = Foo*]’
/usr/include/c++/9/bits/stl_algobase.h:441:30:   required from ‘_OI std::__copy_move_a2(_II, _II, _OI) [with bool _IsMove = false; _II = const Foo*; _OI = Foo*]’
/usr/include/c++/9/bits/stl_algobase.h:474:7:   required from ‘_OI std::copy(_II, _II, _OI) [with _II = const Foo*; _OI = Foo*]’
/usr/include/c++/9/bits/vector.tcc:321:29:   required from ‘void std::vector<_Tp, _Alloc>::_M_assign_aux(_ForwardIterator, _ForwardIterator, std::forward_iterator_tag) [with _ForwardIterator = const Foo*; _Tp = Foo; _Alloc = std::allocator<Foo>]’
/usr/include/c++/9/bits/stl_vector.h:793:2:   required from ‘void std::vector<_Tp, _Alloc>::assign(std::initializer_list<_Tp>) [with _Tp = Foo; _Alloc = std::allocator<Foo>]’
main.cpp:8:30:   required from here
/usr/include/c++/9/bits/stl_algobase.h:382:39: error: static assertion failed: type is not assignable
  382 |    static_assert( __assignable::type::value, "type is not assignable" );

In a less trivial real-world example where I'm trying to reassign some structs with const members to a vector, I run into this issue, although the compiler complains with different messages:

error: cannot bind rvalue reference of type ‘std::optional<long unsigned int>&&’ to lvalue of type ‘const std::optional<long unsigned int>’

and in the same output quite a few of:

error: non-static const member ‘const uint64_t myStruct::m_MyMember’, can’t use default assignment operator
error: no matching function for call to ‘std::optional<long unsigned int>::operator=(const std::optional<long unsigned int>&) const’

Are all those three related? I just want to understand why I can reassign new elements to an existing vector and if it's solely due to my structs not having an explicit copy assignment operator. I suspect the answer is yes since adding my own copy-assignment constructor does bypass the issue in the sample code:

Foo& operator=(Foo other) { return *this; }

But this seems silly and I just want to find a sensible way to reassign these to a vector.

Nobilis
  • 7,310
  • 1
  • 33
  • 67
  • You could wrap your elements with `std::unique_ptr`. This would change the syntax though. – Simon Kraemer Jun 17 '21 at 10:32
  • 1
    it is not only about the vector , `foo1 = foo2` assignment does not work either and `vector::assign` is trying to be effective and reassigning to existing objects. you if don't care much about such efficiency you can simply reassign vector with just `vecFoos = std::vector({ Foo{2} });` – dewaffled Jun 17 '21 at 10:37

2 Answers2

4

The problem with const members is that they disable compiler-generated operator=. So, if you use the vector in a manner that operator= is not needed, or if you provide your own operator= somehow, it all works fine. Generally, you never strictly need const member fields, and they cause a lot of problems with STL containers. It's better to avoid them.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • 2
    This is the most concise answer. To OP: If you really want to protect member data, just make them private and make getter functions return const. – sucksatnetworking Jun 17 '21 at 10:38
  • Is this typically for optimisation reasons, as one comment under my question suggests? – Nobilis Jun 17 '21 at 10:50
  • 1
    @Nobilis if by "optimisation" you mean "not being inefficient for the sake of being general" – Caleth Jun 17 '21 at 11:07
  • @Caleth It was more with the piece of advice that suggested not making member fields const as it causes problems with STL containers and I was just wondering if the underlying reason is that STL containers work better with non-const members for optimisation reasons, [this answer](https://stackoverflow.com/questions/3372487/c-stl-troubles-with-const-class-members) echoes the same point that they cause problems and there seems to be some discussion on that being for optimisation reasons (e.g. using assignment to reuse memory). – Nobilis Jun 17 '21 at 11:09
  • 1
    @Nobilis no, it's not an optimization. Assignment simply doesn't work if the types are not copy assignable. It's about semantics, not perf. – Aykhan Hagverdili Jun 17 '21 at 11:33
  • @AyxanHaqverdili ah, sorry, I meant do the types have to be copy-assignable for performance reasons? just curious about the design – Nobilis Jun 17 '21 at 17:33
  • 1
    @Nobilis they have to be copy-assignable because you try to copy assign them. It's not at all related to performance. If you put your objects into a vector, and never do anything that causes them to be copy-assigned, you won't notice any difference. – Aykhan Hagverdili Jun 17 '21 at 18:04
  • 1
    @AyxanHaqverdili right, makes sense, thank you. – Nobilis Jun 17 '21 at 18:25
1

But this seems silly and I just want to find a sensible way to reassign these to a vector.

You can't assign to a const int anywhere, why would a data member be special? That's what const means.

You are able to construct Foos, which allows you to push_back, as that only requires CopyInsertable, not any kind of assignable.

assign requires CopyAssignable, which is not strictly necessary, but is a hint that implementations should re-use existing elements.

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • But I'm assigning to the vector which is not const and not to the struct's const members, right? So at least on the surface it seems I should be able to fill my vector with new structs and them having const members or not is not relevant to the vector itself. But it sounds like this is an incorrect assumption. – Nobilis Jun 17 '21 at 10:50
  • 1
    "`a.assign(i,j)` Preconditions: `T` is Cpp17EmplaceConstructible into `X` from `*i` and assignable from `*i`." from the [container requirements](https://timsong-cpp.github.io/cppwp/n4861/container.requirements#sequence.reqmts-4) I.e. existing elements may be re-used – Caleth Jun 17 '21 at 11:00