Adding an item to the back of an STL container

I am looking for a general way to add an element to the end of an STL container. I would like the code to support as many types of STL containers as possible. The following code snippet 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 is great for vector s because std::vector implements the emplace function. But std::string not! Is there a more general way to perform the same operation?

+6
source share
3 answers

The most common way (not necessarily the most efficient way):

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

where, of course, value should be suitable for container c (you can use decltype(c)::value_type ). In the case of an associative container, for example, map , this is std::pair .

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


In addition to the comments, here are some advanced things;)

If 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 must find the container of the container type that supports reserve() and calls it before inserting the elements.

The following method correctly defines reserve() (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 for additional reservation of a place. An example might 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 unordered associative containers.

As indicated above, explicit specialization or transshipment is not required for specific types of containers, it also works for non-standard containers if their interfaces are designed quite similarly to the interfaces of standard containers. (In fact, I had several such β€œhomemade” containers in the past and above, Just-Works β„’)

A note on converting in the above code: the reason for converting T to C::value_type is to show that this would be the right place if necessary. In the example above, this may seem redundant, but in my real code, I call a class of special transformations to convert e (which are encoded strings) to the correct value type for any container.

+9
source

Most often, people use traits.

Many library accelerators have solved the same problem, so you could reuse existing features.

Simple demo: 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 a stand- add_to utility function that uses the traits::add_at_end , which can be specialized (in this case, for any vector<...> , set<...> or basic_string<...> template instances.

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

+4
source

push_back supported by std::string , std::vector and std::list . With this, your template template is simple:

 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; } }; 
+2
source

Source: https://habr.com/ru/post/956029/


All Articles