c++ 从标准容器重新绑定和复制分配器

nukf8bse  于 2022-12-30  发布在  其他
关注(0)|答案(1)|浏览(169)

背景:

我想创建一个函数,当给定一个vector V时,它将reference_wrapper s VR的vector返回给V中的每个元素。
这是微不足道的,但是我还希望返回的向量 * 复制反弹分配器 *,以防原始向量有一个(可能是有状态的)自定义分配器。
我想出了下面的MVCE,它在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);
}
5n0oy7gb

5n0oy7gb1#

允许将容器的分配器转换为重新绑定分配器类型(例如用于构造新容器)。然而,对于满足Cpp 17 Allocator要求的类型,只需要能够通过直接初始化进行这种转换。因此,新向量需要如下构造:

auto result = result_type(v.begin(), v.end(), rebound_allocator(v.get_allocator()));

参见[分配器.要求.概述]/69。
这里有一个微妙之处:[allocator.requirements.general]/69仅在rebound_allocator是有效类型时才有效,而rebound_allocator可能不是有效类型(参见[allocator.requirements.general]/96)。另一方面,如果用户期望能够将其分配器类型与诸如std::vector的标准库容器模板一起使用,则他们通常应当确保其分配器可以被重新绑定到任意值类型;参见[container.requirements.pre]/3(暗示反弹分配器可以用于构造和销毁)和[container.reqmts]/64(暗示反弹分配器可能需要用于分配和解除分配)因为该标准没有提供关于容器的“内部类型”可能是什么的保证,只能重新绑定到某些值类型的用户定义分配器不能与标准库容器一起可移植地使用。因此,您可以合理地预期,将从std::vector的专门化获得的分配器转换为反弹类型,并使用该反弹分配器进行分配、构造、销毁和解除分配,无论您要重新绑定到什么值类型,都将起作用。

相关问题