vector<unique_ptr> doesn't have a Random Access Iterator?
By Gašper Ažman
The good book says:
27.2.3 Input Iterators:
Expression | Return type | Semantics |
---|---|---|
*r | convertible to T | |
*r++ | convertible to T | { T tmp = *r; ++r; return tmp; } |
RandomAccessIterator inherits these.
What is
It's False.
(well technically it's derived from std::false_type, but close enough)
is_convertible<unique_ptr<int>&, unique_ptr<int>>{}?
... When you are writing a concepts library ...
error: requirement
Convertible(unique_ptr<int>&, unique_ptr<int>)
not satisfied.
required by InputIterator(vector<unique_ptr<int>>::iterator)
required by ForwardIterator(vector<unique_ptr<int>>::iterator)
required by BidirectionalIterator(vector<unique_ptr<int>>::iterator)
required by RandomAccessIterator(vector<unique_ptr<int>>::iterator)
vector<unique_ptr<int>> v;
v.push_back(make_unique(5));
for_each(v.begin(), v.end(), [](auto&) {});
What's the big deal?
T& | T const& | T | T&& | proxy
T& is not convertible to T for move-only types.
AKA vector<unique_ptr>::iterator
What's the big deal?
T& | T const& | T | T&& | proxy
NOPE
AKA vector<unique_ptr>::const_iterator
What's the big deal?
T& | T const& | T | T&& | proxy
ACTUALLY WORKS!
... with the caveat that:
- there are no such iterators (yet)
- they would move the original values out
What's the big deal?
T& | T const& | T | T&& | proxy
ACTUALLY WORKS!
FOR REAL
AKA move_iterator<vector<unique_ptr>::iterator>
But remember *r++?
{ T tmp = *r;
++r;
return tmp; }
The operational semantics ALWAYS MOVE!
even if you're discarding the value.
What's the big deal?
T& | T const& | T | T&& | proxy
May... or may not.
AKA vector<unique_ptr>{} | filter{predicate}
But remember *r++?
{ T tmp = *r;
++r;
return tmp; }
SAME PROBLEM... Maybe. Or plain doesn't work.
The fix
The good book should say:
27.2.3 Input Iterators:
Expression | Return type | Semantics |
---|---|---|
*r | convertible to T const& | |
*r++ | convertible to T const& | { MAGIC tmp = *r; ++r; return tmp; } |
MAGIC is a problem because...
*r and *r++ may return:
- T
- T const&
- T&
- T&&
FIN.
Because explaining magic is a whole other talk.
Find me after if interested.
Working through MAGIC:
T | proxy| T& | T const& | T&&
*r
*r++
Persistent values (vector)
Generators
Binds to T const&!
{
auto tmp = *r; // move or copy
++r;
return tmp; // move
} -> T
{
auto tmp = *r; // move or copy
++r;
return tmp; // move
} -> T
using T = std::unique_ptr<int>;
struct proxy {
operator T() const {
return make_unique<int>(0);
}
};
struct iterator {
proxy operator*() const {
return proxy{};
}
};
What if *r is a proxy that converts to T?
Yup, works.
#include <type_traits>
#include <memory>
using T = std::unique_ptr<int>;
struct proxy {
operator T() const { return std::make_unique<int>(0); }
};
struct iterator {
proxy operator*() const {
return proxy{};
}
};
int main() {
iterator x;
T const& y = *x;
static_assert(std::is_convertible<decltype(*x),
T const&>::value, "");
}
Potential issues
What if *r is a T&?
T& binds to T const&.
If you need that, you are asking for a mutable iterator, not an InputIterator, and have an extra requirement.
And now for *r++
Input iterators where *r returns a value type
Operational semantics of *r++
{
T tmp = *r;
++r;
return tmp;
}
move
move
The move works because we are not converting.
how do we ask for T const& or move constructible?
T
Current wording
Potential issues
Input iterators where *r returns a T const&
Operational semantics of *r++
{
MAGIC tmp = *r;
++r;
return tmp;
}
{
T tmp = *r;
++r;
return tmp;
}
T
MAGIC needs to be convertible to
T const&, and persist the object IF the iterator doesn't hang on to it.
MAGIC
Current wording
What we need
Potential issues
Input iterators where *r returns a T&
Operational semantics of *r++
{
MAGIC tmp = *r;
++r;
return tmp;
}
{
T tmp = *r;
++r;
return tmp;
}
MAGIC
T
MAGIC needs to be convertible to
T const&, persist the object if necessary, but we also need convertibility to T&&
Current wording
What we need
Potential issues
Input iterators where *r returns a T&&
Operational semantics of *r++
{
MAGIC tmp = *r;
++r;
return tmp;
}
{
T tmp = *r;
++r;
return tmp;
}
Is T&&
T
This *always* moves
MAGIC needs to persist if necessary but convert to T&&, which converts to
T const&.
MAGIC
We only want to move IF ACTUALLY MOVING
We need help with MAGIC definition, but the current one is not good enough.
Conclusion
I also want holes poked into the T const& convertibility, but currently I don't see any.
Potential issues
Algorithms relying on convertibility to T?
There are none. All of them specify convertibility in addition already. (<numeric>, <algorithm> survey done)
deck
By Gašper Ažman
deck
- 698