Контейнер с прокси-итератором/ссылкой и авто

Я реализую контейнер с прокси-итератором/ссылочным типом, похожим на std::vector<bool>, и сталкиваюсь со следующей проблемой, которую я продолжаю иллюстрировать с помощью std::vector<bool> (этот вопрос не о std::vector<bool>!):

#include <vector>
#include <type_traits>
int main() {
  using namespace std;
  vector<bool> vec = {true, false, true, false};
  auto value = vec[2];  // expect: "vector<bool>::value_type"
  const auto& reference = vec[2]; // expect: "vector<bool>::const_reference"

  static_assert(is_same<decltype(value), vector<bool>::value_type>::value, 
                "fails: type is vector<bool>::reference!");
  static_assert(is_same<decltype(reference), 
                        vector<bool>::const_reference>::value,
                "fails: type is const vector<bool>::reference&!"); 

  /// Consequence:
  auto other_value = value; 
  other_value = false; 
  assert(vec[2] == true && "fails: assignment modified the vector");
  • Есть ли способ реализовать тип прокси, чтобы оба статических утверждения проходили?

  • Существуют ли какие-либо рекомендации по решению этой проблемы при реализации такого контейнера?

Может быть, с помощью оператора преобразования в auto/auto&/auto&&/const auto...?

EDIT: пример переработан, чтобы сделать его более понятным. Спасибо @LucDanton за его комментарий ниже.


person Community    schedule 21.11.2013    source источник
comment
Вы знаете, что специализация std::vector<bool> работает не так, как любая другой std::vector?   -  person Some programmer dude    schedule 21.11.2013
comment
@JoachimPileborg да, std::vector<bool> не является стандартным контейнером. Он очень четко иллюстрирует использование прокси-итераторов/ссылочных типов и их подводные камни. Я в основном спрашиваю, есть ли способ избежать их.   -  person gnzlbg    schedule 21.11.2013
comment
Вы также должны добавить тег [C++], см. информационную страницу тега C++11 -- но тогда вам пришлось удалить один из других тегов.   -  person dyp    schedule 21.11.2013
comment
Я не видел decltype(auto) var = init;. Это похоже на auto&& var = init;?   -  person aschepler    schedule 21.11.2013
comment
Нашел свой собственный ответ: en.wikipedia.org/wiki/   -  person aschepler    schedule 21.11.2013
comment
Что ж, не хочу показаться нубом, но decltype((bool)value) будет иметь проход static_assert для clang, хотя я сомневаюсь, что это имеет какое-либо отношение к вопросу.   -  person    schedule 21.11.2013
comment
@remyabel правильно, это сделает static_assert проходным. Вопрос в том, есть ли способ заставить его пройти без явного преобразования. Пользователю не нужно знать, что он получает не логическое значение, а тип прокси.   -  person gnzlbg    schedule 21.11.2013
comment
При возврате по значению (скорее всего так и при возврате прокси) auto и decltype(auto) совпадают. Если вы хотите, чтобы они различались, вы столкнетесь с сомнительными соображениями по поводу времени жизни (например, где будет находиться прокси-объект и как долго?).   -  person Luc Danton    schedule 21.11.2013
comment
@LucDanton да, точно. Я добавил вашу точку зрения к вопросу и еще один пример, чтобы прояснить проблему. По сути, мне нужно что-то вроде операторов преобразования в auto, auto&,... Затем я контролирую, где находятся прокси-объекты, поэтому проблемы со сроком службы исчезают.   -  person gnzlbg    schedule 22.11.2013
comment
Я нашел этот документ, в котором может быть реализована такая функция, поэтому я не думаю, что хорошее решение возможно: open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3748.pdf   -  person gnzlbg    schedule 22.11.2013


Ответы (2)


Прокси и auto плохо взаимодействуют именно потому, что auto раскрывает информацию о типах, которые должны были оставаться скрытыми.

Было несколько запросов на интерес к вещам в стиле operator auto (в основном, «при выводе меня как типа используйте вместо этого тип»), но, насколько я знаю, ни один из них даже не дошел до официального предложения.

Другая проблема заключается в том, что vector<bool> является неожиданным, поскольку это единственный экземпляр vector, использующий прокси. Были и другие предварительные предложения, призывающие к тому, чтобы класс vector<bool> был признан устаревшим и в конечном итоге стал неспециальным, а на его место был введен специальный класс bitvector.

person Sebastian Redl    schedule 22.11.2013
comment
Я пишу контейнер, который должен вернуть прокси. Могу ли я что-нибудь сделать, чтобы избавить своих пользователей от этих ловушек? - person gnzlbg; 22.11.2013
comment
Нет. Прокси не полностью прозрачны, и auto делает их еще менее прозрачными, чем раньше. Это слабость языка. - person Sebastian Redl; 22.11.2013

Как известно, vector<bool> имеет нестандартный интерфейс по сравнению с основным шаблоном vector<T>.

Соответствующие различия заключаются в том, что вложенные типы reference и const_reference являются typedef для T& и T const& в общем случае, а для прокси-класса reference и типа значения bool для vector<bool> .

При доступе к векторным элементам также важно помнить, что постоянство векторного объекта определяет, что operator[] возвращает reference или const_reference. Кроме того, auto отбрасывает ссылочные квалификаторы, а decltype сохраняет их.

Давайте посмотрим на неконстантный/константный вектор bool/int и используем auto, decltype(auto) и auto const& (обычный auto& приведет к проблемам в реальном времени для прокси). Вы получаете следующее поведение:

#include <vector>
#include <type_traits>
#include <typeinfo>
#include <iostream>
#include <ios>

int main() {
  using namespace std;

  vector<bool> vb = { true, false, true, false };
  vector<int > vi = {    1,     0,    1,     0 };

  auto vb2 = vb[2];             // vector<bool>::reference != bool
  auto vi2 = vi[2];             // int
  decltype(auto) rvb2 = vb[2];  // vector<bool>::reference
  decltype(auto) rvi2 = vi[2];  // int&
  auto const& crvb2 = vb[2];    // vector<bool>::reference const& != bool const&
  auto const& crvi2 = vi[2];    // int const&

  auto ovb2 = vb2;
  ovb2 = false;                 // OOPS ovb2 has reference semantics
  cout << boolalpha << (vb[2] == true) << "\n";

  auto ovi2 = vi2;
  ovi2 = 0;                     // OK, ovi2 has value semantics
  cout << boolalpha << (vi[2] == 1) << "\n";

  static_assert(is_convertible<decltype(vb2),   vector<bool>::value_type>::value, "");  
  static_assert(is_same       <decltype(vi2),   vector<int >::value_type>::value, "");
  static_assert(is_same       <decltype(rvb2),  vector<bool>::reference>::value, "");  
  static_assert(is_same       <decltype(rvi2),  vector<int >::reference>::value, "");
  static_assert(is_convertible<decltype(crvb2), vector<bool>::const_reference>::value, "");  
  static_assert(is_same       <decltype(crvi2), vector<int >::const_reference>::value, "");

  vector<bool> const cvb = { true, false, true, false };
  vector<int > const cvi = {    1,     0,    1,     0 };   

  auto cvb2 = cvb[2];            // vector<bool>::const_reference == bool
  auto cvi2 = cvi[2];            // int
  decltype(auto) rcvb2 = cvb[2]; // vector<bool>::const_reference == bool
  decltype(auto) rcvi2 = cvi[2]; // int const&
  auto const& crcvb2 = cvb[2];   // vector<bool>::reference const& != bool const&
  auto const& crcvi2 = cvi[2];   // int const&

  static_assert(is_same       <decltype(cvb2),   vector<bool>::value_type>::value, "");  
  static_assert(is_same       <decltype(cvi2),   vector<int >::value_type>::value, "");
  static_assert(is_same       <decltype(rcvb2),  vector<bool>::const_reference>::value, "");  
  static_assert(is_same       <decltype(rcvi2),  vector<int >::const_reference>::value, "");
  static_assert(is_convertible<decltype(crcvb2), vector<bool>::const_reference>::value, "");  
  static_assert(is_same       <decltype(crcvi2), vector<int >::const_reference>::value, "");

  auto ocvb2 = cvb2;
  ocvb2 = false;                 // OK, ocvb2 has value semantics
  cout << boolalpha << (cvb[2] == true) << "\n";

  auto ocvi2 = cvi2;
  ocvi2 = 0;                     // OK, ocvi2 has value semantics
  cout << boolalpha << (cvi[2] == 1) << "\n";  
}

Живой пример

Обратите внимание, что для неконстантного vector<bool> использование auto на operator[] даст вам эталонный прокси, который не имеет семантики значений. Использование const vector<bool> позволит избежать этого. Я не вижу, как это можно решить по-другому.

auto const& поведенчески эквивалентен, но имеет is_convertible, а не is_same внутри static_assert. Я думаю, что это лучшее, что можно сделать.

Обратите внимание, что для общей итерации и алгоритмов STL на прокси-контейнерах все не так безрадостно. См. колонку Хиннанта по этому поводу.

person TemplateRex    schedule 22.11.2013
comment
проблема в том, что auto value имеет тип vector<bool>::reverence вместо vector<bool>::value_type. Только auto& reference должен иметь тип vector<bool>::reference, но сейчас он имеет тип vector<bool>::reference&, что тоже неверно. - person gnzlbg; 22.11.2013
comment
@gnzlbg да, и это неизбежно, потому что auto следует правилам вывода аргументов шаблона, которые, в свою очередь, не учитывают последовательности преобразования (например, определяемое пользователем преобразование vector<bool>::reference в bool). Но это также не имеет большого значения, поскольку вы можете смягчить свое утверждение, чтобы проверить только конвертируемость типов, а не равенство типов. Вот что такое прокси: использовать что-то еще за вашей спиной, не мешая вам. - person TemplateRex; 22.11.2013
comment
авто v = vec[1]; v = другое; не изменяет значение контейнера, если вы не получаете прокси. В этом случае он изменяет значение в контейнере! Так что да, тот факт, что auto следует за TAD, противоречит цели использования прокси, потому что он мешает вам неочевидным образом. Более того, пользователю на самом деле нужно знать, что он получает прокси, чтобы избежать этой ловушки. - person gnzlbg; 22.11.2013
comment
Я добавил к вопросу раздел consequence, объясняющий это. - person gnzlbg; 22.11.2013
comment
@gnzlbg tnx, я обновил свой ответ систематическим исследованием расхождений между vector<bool> и vector<int>. - person TemplateRex; 22.11.2013