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

Geo*_*ard 7 c++ stl c++11 c++14

我正在寻找一种将元素添加到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
}
Run Code Online (Sandbox Code Playgroud)

这里的问题很微妙.此代码适用于vectors,因为std::vector实现了该emplace功能.但std::string不是!是否有更通用的方法来执行相同的操作?

Dan*_*rey 10

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

c.insert( c.end(), value );
Run Code Online (Sandbox Code Playgroud)

当然,value需要适合容器c(您可以使用decltype(c)::value_type).在关联容器的情况下,例如map,它是a 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
{};
Run Code Online (Sandbox Code Playgroud)

现在您可以使用它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
  }
}
Run Code Online (Sandbox Code Playgroud)

add_array现在可以与所有标准集装箱(除了所谓的std::forward_list),它会调用reserve()std::vector和无序关联容器.

由于上面不需要对特定容器类型进行显式专门化或重载,因此它也适用于非标准容器,只要它们的接口设计合理地类似于标准容器的接口即可.(事实上​​我过去有过几个这样的"自制"容器和上面的Just-Works™)

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

  • 编译器几乎肯定会内联c.end(),因此它将等效于直接到达容器的结束指针或大小值 - 但是它已被实现,不太可能涉及函数调用并试图自己保存这些信息只是复制信息和可能的错误来源 (2认同)
  • @thirtythreeforty实际上,如果你关心性能,你想要做一件事:如果你知道要添加多少元素,检测容器是否有"reserve()"并提前调用它.要检测`reserve()`,你可以参考[我在SO上询问的第一个问题以及它为我开始的地方及其答案](http://stackoverflow.com/questions/14882588).事实上,我正在研究这个确切的问题:如何使用值填充标准容器. (2认同)

seh*_*ehe 5

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

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

一个简单的演示:住在Coliru

#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, '?');
}
Run Code Online (Sandbox Code Playgroud)

基本上,你做了什么,是有一个独立的效用函数add_to,它使用一个特征类traits::add_at_end可专门(在这种情况下任何vector<...>,set<...>basic_string<...>模板实例.

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