0

I'm trying to define a constant list of constant objects and I can't seem to get it done. Here is my example that compiles fine:

#include <string>
#include <list>

class Person { public:
    std::string name;
    Person(const std::string &in_name){name=in_name;}
};

class MyClass { public:
    const std::list</* const */Person> l;
    MyClass(const std::list</* const */Person> &in_l):l(in_l){}
};

int main(int argc, char **argv) {
    Person dave("dave");
    MyClass c(std::list<const Person>(dave));
    return 0;
}

When I remove the comments from const in those 2 places, I get the following errors:

In file included from /usr/include/x86_64-linux-gnu/c++/7/bits/c++allocator.h:33:0,
                 from /usr/include/c++/7/bits/allocator.h:46,
                 from /usr/include/c++/7/string:41,
                 from main66.cpp:1:
/usr/include/c++/7/ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<const Person>’:
/usr/include/c++/7/bits/allocator.h:108:11:   required from ‘class std::allocator<const Person>’
main66.cpp:11:53:   required from here
/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 Person; __gnu_cxx::new_allocator<_Tp>::const_pointer = const Person*; __gnu_cxx::new_allocator<_Tp>::const_reference = const Person&]’ 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 Person; __gnu_cxx::new_allocator<_Tp>::pointer = const Person*; __gnu_cxx::new_allocator<_Tp>::reference = const Person&]’
       address(reference __x) const _GLIBCXX_NOEXCEPT
       ^~~~~~~

Is there any way to define a std::list of const objects?

OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87
  • I don't think you can have such a list. You can have a `const std::list` or a `std::list` but the list need to be able to modify the person values to be a container of those values. – Jazzwave06 Feb 16 '20 at 15:21
  • 3
    OP, please be aware that `MyClass c(std::list(dave));` does not do what you think it does. This is a function declaration, not a variable definition. Use `{}` instead of `()` to make it do what you want it to do. – walnut Feb 16 '20 at 15:32
  • Related/duplicate: https://stackoverflow.com/questions/6954906/ – walnut Feb 16 '20 at 16:54

3 Answers3

9

Allocator-aware containers, such as std::list, cannot take const value types, because the Allocator requirements specify behavior only for cv-unqualified types. This means that the container is not guaranteed to be able to create the element objects through the allocator interface if the value type is const or volatile qualified.

This is not a problem though, because the container being const is enough to guarantee that the elements are not changed. If you access an element of the container through a const reference to the container, you will only ever get a const reference to the element.

So, just use const std::list<Person>, instead of const std::list<const Person>.

Technically someone could const_cast the constness away from such a reference to be able to modify the elements and that would probably be legal, i.e. not undefined behavior, but that is something that a user can always do, just that it would cause undefined behavior with const objects.

See also Does C++11 allow vector<const T>? for details.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • But then elements are not `const`, so someone could use `const_cast`, thus most optimizations cannot be applied. Is it really the same thing? – Aykhan Hagverdili Feb 16 '20 at 15:54
  • @Ayxan Yes, probably that would be legal. But a user can always do that. The only difference is whether it will work or be UB, so I don't think that is really a concern. – walnut Feb 16 '20 at 15:56
  • If that's the case, then why did the explicit restriction on const value types was removed post-C++11? I wonder if you can use a list of `const Person`, but with a `std::allocator`. – Sam Varshavchik Feb 16 '20 at 15:56
  • *"The only difference is whether it will work or be UB"* If it is UB, then compiler can ignore those cases and apply appropriate optimizations. If it is legal, the compiler cannot do that. – Aykhan Hagverdili Feb 16 '20 at 15:57
  • @SamVarshavchik https://gcc.godbolt.org/z/vMVwLg `std::list must have the same value_type as its allocator` and `std::list must have a non-const, non-volatile value_type` – Aykhan Hagverdili Feb 16 '20 at 16:00
  • @SamVarshavchik I don't think there was any restriction specifically against `const` types? The removable of the *CopyAssignable* requirement is probably more related to e.g. classes with `const` or reference members, but not to top-level `const` qualification. I don't think using a different allocator type will work, because the allocator requirements only require the allocator to work with `T*`, not `const T*`. – walnut Feb 16 '20 at 16:04
  • @SamVarshavchik More concretely, there is an explicit requirement that the allocator value_type and the container value_type are identical: https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer – walnut Feb 16 '20 at 16:11
  • 1
    @Ayxan I think there isn't really anything to optimize, because the user could also placement-new completely different objects into the storage, even if it was `const`. Doing so is only prohibited for non-dynamic storage duration objects: https://eel.is/c++draft/basic#life-10 – walnut Feb 16 '20 at 16:16
2

std::list must have a non-const, non-volatile value_type.

2

Error message seems clear enough https://gcc.godbolt.org/z/MG3Kxv:

error: static assertion failed: std::list must have a non-const, non-volatile value_type

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93