I wanted to create a function which, when given a vector
V, returns a vector
of reference_wrapper
s VR to each element in V.
This is trivial, but I also wanted the returned vector to copy the rebound allocator, in case the original vector has a (possibly stateful) custom allocator.
I came up with the MVCE below, which compiles on godbolt.
#include <algorithm>
#include <string>
#include <vector>
#include <functional>
#include <type_traits>
// A class to make my nasty string unmoveable, and therefore expensive
// to sort
struct Nasty
{
Nasty(std::string s) : value_(std::move(s)) {}
Nasty(Nasty const&) = default;
Nasty& operator=(Nasty const&) = default;
auto value() const -> std::string const& {
return value_;
}
std::string value_;
};
// function object class to deduce types and perform the conversion
template<class V>
struct as_references_op
{
using vector_type = V;
using value_type = typename V::value_type;
static constexpr bool is_const = std::is_const<vector_type>::value;
using referred_type = std::conditional_t<is_const, std::add_const_t<value_type>, value_type>;
using allocator_type = typename V::allocator_type;
using a_traits = std::allocator_traits<allocator_type>;
using rebound_allocator = typename a_traits::template rebind_traits<referred_type>::allocator_type;
using result_type = std::vector<std::reference_wrapper<referred_type>, rebound_allocator>;
result_type operator()(V& v) const
{
auto result = result_type(v.begin(), v.end(), v.get_allocator());
return result;
}
};
// interface functions
template<class T, class A>
decltype(auto) as_references(std::vector<T, A> const& v)
{
auto op = as_references_op<std::vector<T, A> const>();
return op(v);
}
template<class T, class A>
decltype(auto) as_references(std::vector<T, A>& v)
{
auto op = as_references_op<std::vector<T, A>>();
return op(v);
}
// test
int main()
{
auto v = std::vector<Nasty> { Nasty("gah"), Nasty("bar"), Nasty("zoo"), Nasty("foo") };
auto sv = as_references(v);
auto by_dereferenced_value = [] (auto&& x, auto&& y) {
return x.get().value() < y.get().value();
};
std::sort(begin(sv), end(sv), by_dereferenced_value);
}
It is permitted to convert a container's allocator to a rebound allocator type (e.g. for use in constructing a new container). However, for a type to meet the Cpp17Allocator requirements, it is only required that such conversion be possible through direct-initialization. As such, the new vector would need to be constructed like so:
auto result = result_type(v.begin(), v.end(), rebound_allocator(v.get_allocator()));
See [allocator.requirements.general]/69.
There is a subtlety here: [allocator.requirements.general]/69 holds only if rebound_allocator
is a valid type, which it might not be (see [allocator.requirements.general]/96). On the other hand, if the user expects to be able to use their allocator type with a standard library container template such as std::vector
, then they generally should ensure that their allocator can be rebound to an arbitrary value type; see [container.requirements.pre]/3 (implying that a rebound allocator may be used for construction and destruction) and [container.reqmts]/64 (implying that a rebound allocator may need to be used for allocation and deallocation). Because the standard provides no guarantees as to what the container's "internal type" might be, a user-defined allocator that can only be rebound to certain value types cannot portably be used with standard library containers. Consequently, it is reasonable for you to expect that converting an allocator obtained from a specialization of std::vector
into a rebound type, and using that rebound allocator for allocation, construction, destruction, and deallocation, will work regardless of what value type you are rebinding to.