Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding element to back of STL container

Tags:

c++

c++11

stl

c++14

I'm looking for a general way of adding an element to the back of an STL container. I would like the code to support as many types of STL container as possible. The following piece of code demonstrates my problem:

#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
}

The problem here is subtle. This code works great for vectors, because std::vector implements the emplace function. But std::string does not! Is there a more general way to perform the same operation?

like image 598
George Hilliard Avatar asked Oct 15 '13 22:10

George Hilliard


2 Answers

The most generic way (not necessarily the most efficient way) is:

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

where, of course, value needs to be suitable for the container c (you may use decltype(c)::value_type). In case of an associative container, e.g. map, it is a std::pair.

This works for all standard containers except for std::forward_list. For some containers the element is then added at the end, for some the c.end() is just a hint that might be ignored.


As a follow up to the comments, here's the advanced stuff ;)

When you want to insert a known number of elements into a given container c (of type C) and you want to be at least somewhat efficient, you should detect wether the container type supports reserve() and call it before inserting elements.

The following method detects reserve() correctly (the link explains how):

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
{};

Now you can use it with std::enable_if_t to optionally reserve space. An example could look like this:

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
  }
}

add_array can now be called with all standard containers (except std::forward_list) and it will call reserve() for std::vector and the unordered associative containers.

As the above does not need explicit specialization or overloading for specific container types, it also works for non-standard containers as long as their interfaces are designed reasonably similar to the standard containers' interfaces. (In fact I had several such "home-made" containers in the past and the above Just-Works™)

A remark about the conversion in the above code: The reason for converting the Ts to C::value_type is just to show that this would be the correct place if it is needed. In the above example it might look superfluous, but in my real-world code I call a special conversion traits class to convert the es (which are encoded strings) into the correct value type for any container.

like image 50
Daniel Frey Avatar answered Sep 23 '22 04:09

Daniel Frey


Most often, people use traits.

Many boost libraries have solved this same problem, so you might be able to reuse existing traits.

A simple demonstration: Live on 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, '?');
}

Basically, what you do, is have a free-standing utility function add_to that uses a trait class traits::add_at_end that can be specialized (in this case for any vector<...>, set<...>, or basic_string<...> template instances.

In practice, you would share the implementation for similar containers (e.g. deque and vector) by inheriting the common implementation.

like image 28
sehe Avatar answered Sep 23 '22 04:09

sehe