将元素添加到 STL 容器的背面



我正在寻找一种将元素添加到 STL 容器背面的通用方法。 我希望代码支持尽可能多的 STL 容器类型。 以下代码段演示了我的问题:

#include <vector>
#include <string>
using namespace std;
template<typename T>
class S {
  T built;
  typename T::iterator built_it;
public:
  S() : built{}, built_it{built.end()} {}
  void add_to(typename T::value_type e) {
    built.emplace(built_it, e);
    ++built_it;
  }
  const T& get() {
    return built;
  }
};
int main()
{ 
  S<std::vector<int>> e;
  S<std::string> f;
  e.add_to(3);   // works
  f.add_to('c'); // doesn't
}

这里的问题是微妙的。 此代码非常适合vector,因为std::vector实现了emplace函数。 但std::string没有! 是否有更通用的方法来执行相同的操作?

最通用的方法(不一定是最有效的方法)是:

c.insert( c.end(), value );

当然,value需要适合容器c(您可以使用decltype(c)::value_type)。如果是关联容器,例如 map,这是一个std::pair

这适用于所有标准容器,但 std::forward_list 除外。对于某些容器,元素随后添加到末尾,对于某些容器,c.end()只是一个可能被忽略的提示。


作为评论的后续,这是高级内容;)

当您想要将已知数量的元素插入到给定的容器c(类型 C)中并且您希望至少保持一定的效率时,您应该检测容器类型是否支持reserve()并在插入元素之前调用它。

以下方法正确检测reserve()(链接说明了如何):

template< typename C, typename = void >
struct has_reserve
  : std::false_type
{};
template< typename C >
struct has_reserve< C, std::enable_if_t<
                         std::is_same<
                           decltype( std::declval<C>().reserve( std::declval<typename C::size_type>() ) ),
                           void
                         >::value
                       > >
  : std::true_type
{};

现在,您可以将其与std::enable_if_t一起使用,以选择性地保留空间。示例可能如下所示:

template< typename C >
std::enable_if_t< !has_reserve< C >::value >
optional_reserve( C&, std::size_t ) {}
template< typename C >
std::enable_if_t< has_reserve< C >::value >
optional_reserve( C& c, std::size_t n )
{
  c.reserve( c.size() + n );
}
template< typename C, typename T, std::size_t N >
void add_array( C& c, const std::array< T, N >& a )
{
  optional_reserve( c, N );
  for( const auto& e : a ) {
    c.insert( c.end(), typename C::value_type( e ) ); // see remark below
  }
}

现在可以调用所有标准容器(std::forward_list除外)add_array,它将为std::vector和无序关联容器调用reserve()

由于上述内容不需要针对特定容器类型的显式专用化或重载,因此只要其接口的设计与标准容器的接口合理相似,它也适用于非标准容器。(事实上,我过去有几个这样的"自制"容器和上面的Just-Works™)

关于上述代码中转换的注释:将T转换为C::value_type的原因只是为了表明如果需要,这将是正确的位置。在上面的示例中,它可能看起来是多余的,但在我的实际代码中,我调用了一个特殊的转换特征类来将e(编码字符串)转换为任何容器的正确值类型。

大多数情况下,人们使用特征。

许多 boost 库已经解决了同样的问题,因此您可以重用现有的特征。

一个简单的演示:住在科里鲁

#include <vector>
#include <set>
#include <string>
namespace traits
{
    template <typename Container, typename Enable = void>
        struct add_at_end;
    template <typename... TAs>
        struct add_at_end<std::vector<TAs...> > 
        {
            using Container = std::vector<TAs...>;
            template <typename... CtorArgs>
            static void apply(Container& container, CtorArgs&&... args) {
                container.emplace_back(std::forward<CtorArgs>(args)...);
            }
        };
    template <typename... TAs>
        struct add_at_end<std::set<TAs...> > 
        {
            using Container = std::set<TAs...>;
            template <typename... CtorArgs>
            static void apply(Container& container, CtorArgs&&... args) {
                container.insert(container.end(), { std::forward<CtorArgs>(args)...});
            }
        };
    template <typename... TAs>
        struct add_at_end<std::basic_string<TAs...> > 
        {
            using Container = std::basic_string<TAs...>;
            template <typename... CtorArgs>
            static void apply(Container& container, CtorArgs&&... args) {
                container.insert(container.end(), { std::forward<CtorArgs>(args)...});
            }
        };
}
template <typename Container, typename... CtorArgs>
    void add_to(Container& container, CtorArgs&&... args) {
        traits::add_at_end<Container>::apply(container, std::forward<CtorArgs>(args)...);
    }
int main()
{
    using X = std::pair<int, std::string>;
    std::vector<X> v;
    std::set<X>    s;
    std::wstring   wstr;
    std::string    str;
    add_to(v, 12, "hello");
    add_to(s, 42, "world");
    add_to(wstr, L'!');
    add_to(str, '?');
}

基本上,你所做的是拥有一个独立的效用函数add_to,它使用一个特质类traits::add_at_end,该类可以是专门的(在这种情况下用于任何vector<...>set<...>basic_string<...>模板实例)。

在实践中,您将共享类似容器的实现(例如 dequevector)通过继承通用实现。

push_backstd::stringstd::vectorstd::list 支持。 有了这个,您的类模板很简单:

template<typename T>
class S {
  T built;
public:
  S() : built{} {}
  void add_to(typename T::value_type e) {
    built.push_back(e);
  }
  const T& get() {
    return built;
  }
};

最新更新