Reputation: 7415
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");
Is there a way to implement a proxy type such that both static assert's pass?
Are there any guidelines about how to deal with this issue when implementing such a container?
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
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
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";
}
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