С++ 11 устранил требование, чтобы тип значения всех контейнеров был CopyConstructible и Assignable (хотя конкретные операции с контейнерами могут налагать эти требования). Теоретически это должно позволить определить, например, std::deque<const Foo>
, что невозможно в С++ 03.
Неожиданно gcc 4.7.2 произвел свою обычную рвоту непонятных ошибок [1], когда я это пробовал, но clang, по крайней мере, сделал ошибку, читаемую и clang с libС++, скомпилировал ее без ошибок.
Теперь, когда два разных компилятора производят разные результаты, это всегда заставляет меня задаться вопросом, какой правильный ответ, и поэтому я искал все ссылки, которые я мог найти для констант/назначаемых/типов значений/контейнеров и т.д. и т.д. Я нашел почти десятилетние ответы на очень похожие вопросы и ответы, некоторые из них здесь, на SO и другие в различных списках рассылки С++, среди других мест, в том числе Buganizer Gnu, все из которых в основном можно обобщить как следующий диалог.
В: Почему я не могу объявить std::vector<const int>
(как упрощенный пример)
A: Почему бы вам это сделать? Это бессмысленно.
Q: Ну, это имеет смысл для меня. Почему я не могу это сделать?
A: Поскольку Стандарт требует назначения типов значений.
Q: Но я не планирую назначать их. Я хочу, чтобы они были const после того, как я их создал.
A: Это не так, как это работает. Следующий вопрос!
с мягким лишением:
A2: С++ 11 решил разрешить это. Тебе просто придется подождать. В то же время переосмыслите свой смешной дизайн.
Они не кажутся очень неотразимыми ответами, хотя, возможно, я предвзятый, потому что я отношусь к категории "но это имеет смысл для меня". В моем случае я хотел бы иметь контейнер, подобный стеку, в котором вещи, помещенные в стек, являются неизменными до тех пор, пока они не будут всплыты, что не кажется мне особенно странным, чтобы иметь возможность выразить с помощью типа система.
В любом случае, я начал думать об ответе: "Стандарт требует, чтобы все типы значений контейнеров были назначаемыми". И, насколько я вижу, теперь, когда я нашел старую копию проекта стандарта С++ 03, это правда; он сделал.
С другой стороны, тип значения std::map
- это std::pair<const Key, T>
, который не похож на меня как назначаемый. Тем не менее, я снова попробовал std::deque<std::tuple<const Foo>>
, и gcc продолжал компилировать его, не моргнув глазом. Поэтому, по крайней мере, у меня есть какое-то решение.
Затем я попробовал распечатать значение std::is_assignable<const Foo, const Foo>
и std::is_assignable<std::tuple<const Foo>, const std::tuple<const Foo>>
, и оказывается, что первое сообщение считается не назначаемым, как и следовало ожидать, но последнее сообщается как присваиваемое (как clang, так и НКА). Конечно, он не может быть назначен; попытка скомпилировать a = b;
отклоняется gcc с жалобой error: assignment of read-only location
(это было всего лишь единственное сообщение об ошибке, с которым я столкнулся в этом квесте, что было действительно легко понять). Однако, не пытаясь выполнить задание, оба clang и gcc одинаково счастливы создать экземпляр deque<const>
, и код, кажется, работает нормально.
Теперь, если std::tuple<const int>
действительно назначается, то я не могу жаловаться на несогласованность в стандарте C++03
- и, действительно, кому это интересно - но я нахожу, что это беспокоит то, что отчеты по двум различным стандартным библиотекам что тип присваивается, если на самом деле попытка присвоить ссылку ему приведет к (очень разумной) ошибке компилятора. Я мог бы в какой-то момент захотеть использовать тест в шаблоне SFINAE и, основываясь на том, что я видел сегодня, выглядит не очень надежным.
Так есть ли кто-нибудь, кто может пролить свет на вопрос (в заголовке): что означает Assignable? И два бонусных вопроса:
1) Действительно ли комитет имел в виду позволить экземпляры контейнеров с типами значений const
или имел ли они какой-то другой неприемлемый случай?, и
2) Действительно ли существует значительная разница между константами const Foo
и std::tuple<const Foo>
?
[1] Для действительно любопытного здесь сообщение об ошибке, созданное gcc при попытке скомпилировать декларацию std::deque<const std::string>
(с добавлением нескольких строк и объяснение, если вы прокрутите вниз достаточно далеко):
In file included from /usr/include/x86_64-linux-gnu/c++/4.7/./bits/c++allocator.h:34:0,
from /usr/include/c++/4.7/bits/allocator.h:48,
from /usr/include/c++/4.7/string:43,
from /usr/include/c++/4.7/random:41,
from /usr/include/c++/4.7/bits/stl_algo.h:67,
from /usr/include/c++/4.7/algorithm:63,
from const_stack.cc:1:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<const std::basic_string<char> >’:
/usr/include/c++/4.7/bits/allocator.h:89:11: required from ‘class std::allocator<const std::basic_string<char> >’
/usr/include/c++/4.7/bits/stl_deque.h:489:61: required from ‘class std::_Deque_base<const std::basic_string<char>, std::allocator<const std::basic_string<char> > >’
/usr/include/c++/4.7/bits/stl_deque.h:728:11: required from ‘class std::deque<const std::basic_string<char> >’
const_stack.cc:112:27: required from here
/usr/include/c++/4.7/ext/new_allocator.h:83:7:
error: ‘const _Tp* __gnu_cxx::new_allocator< <template-parameter-1-1> >::address(
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference) const [
with _Tp = const std::basic_string<char>;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_pointer =
const std::basic_string<char>*;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference =
const std::basic_string<char>&]’ cannot be overloaded
/usr/include/c++/4.7/ext/new_allocator.h:79:7:
error: with ‘_Tp* __gnu_cxx::new_allocator< <template-parameter-1-1> >::address(
__gnu_cxx::new_allocator< <template-parameter-1-1> >::reference) const [
with _Tp = const std::basic_string<char>;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::pointer = const std::basic_string<char>*;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::reference = const std::basic_string<char>&]’
Так что здесь происходит то, что стандарт (& sect; 20.6.9.1) настаивает на том, что распределитель по умолчанию имеет функции-члены:
pointer address(reference x) const noexcept;
const_pointer address(const_reference x) const noexcept;
но если вы создаете экземпляр с аргументом шаблона const
(который, по-видимому, UB), то reference
и const_reference
являются одним и тем же типом, поэтому объявления дублируются. (Тело определения идентично, для чего оно стоит.) Следовательно, никакой контейнер с поддержкой allocator не может иметь явно тип значения const
. Скрытие const
внутри a tuple
позволяет компоновщику создавать экземпляр. Требование этого распределителя от стандарта использовалось для оправдания закрытия хотя бы нескольких старых ошибок libstdС++ о проблемах с std::vector<const int>
, хотя это не кажется мне солидной принципиальной точкой. Кроме того, libС++ работает с проблемой очевидным простым способом, который заключается в предоставлении специализации allocator<const T>
с удалением дублированных деклараций функций.