gnzlbg
gnzlbg

Reputation: 7415

Container with proxy iterator/reference and auto

I'm implementing a container with a proxy iterator/reference type similar to std::vector<bool> and clash into the following issue, which I proceed to exemplify with std::vector<bool> (this question is not about 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");

Maybe by using a conversion operator to auto/auto&/auto&&/const auto...?

EDIT: reworked the example to make it more clear. Thanks to @LucDanton for his comment below.

Upvotes: 10

Views: 4504

Answers (2)

Sebastian Redl
Sebastian Redl

Reputation: 71999

Proxies and auto don't interact well, precisely because auto reveals things about the types that were supposed to stay hidden.

There have been some requests for interest for operator auto-style things (basically, "when deducing me as a type, use this type instead"), but AFAIK none of them even made it to an official proposal.

The other problem is that vector<bool> is unexpected because it's the only instantiation of vector that uses proxies. There have been other preliminary proposals calling for vector<bool> to be deprecated and eventually revert to being non-special, with a special-purpose bitvector class introduced to take its place.

Upvotes: 5

TemplateRex
TemplateRex

Reputation: 70526

As is well-known, vector<bool> has a non-generic interface compared to the primary template vector<T>.

The relevant differences are that the nested types reference and const_reference are a typedef for T& and T const& in the general case, and to the proxy class reference and the value type bool for vector<bool>.

When accessing vector elements, it is also important to remember that the constness of the vector object determines whether a reference or const_reference is being returned by operator[]. Furthermore, auto will drop the reference qualifiers whereas decltype will keep those.

Let's look at a non-const / const vector of bool / int, and use auto, decltype(auto) and auto const& (plain auto& will lead to live-time issues for proxies). You get the following behavior:

#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";  
}

Live Example

Note that for a non-const vector<bool>, using auto on operator[] will give you a reference proxy that does not have value semantics. Using a const vector<bool> will avoid that. I don't see how this can be solved in any other way.

The auto const& is behaviorally equivalent but has a is_convertible rather than is_same inside the static_assert. I think this is the best one can do.

Note that for general iteration and STL algorithms on proxy containers, things are not so bleak. See Hinnant's column on this.

Upvotes: 4

Related Questions