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 vector
s, because std::vector
implements the emplace
function. But std::string
does not! Is there a more general way to perform the same operation?
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 T
s 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 e
s (which are encoded strings) into the correct value type for any container.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With