我正在寻找在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
没有!是否有更通用的方法来执行相同的操作?
答案 0 :(得分:10)
最generic way(不一定是最有效的方式)是:
c.insert( c.end(), value );
当然,value
需要适合容器c
(您可以使用decltype(c)::value_type
)。在关联容器的情况下,例如map
,std::pair
。
除了std::forward_list
之外,这适用于所有标准容器。对于某些容器,然后在末尾添加元素,因为c.end()
只是一个可能被忽略的提示。
作为评论的后续内容,这是高级内容;)
如果要将已知数量的元素插入到给定容器c
(类型为C
)并且您希望至少在某种程度上有效,则应该检测容器类型是否支持reserve()
并在插入元素之前调用它。
以下方法detects reserve()
correctly(链接说明如何):
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
}
}
现在可以使用所有标准容器调用{p> add_array
(std::forward_list
除外),它将为reserve()
和无序关联容器调用std::vector
。
由于上面不需要对特定容器类型进行显式特化或重载,因此它也适用于非标准容器,只要它们的接口设计合理地类似于标准容器的接口即可。 (事实上,我过去有过几个这样的“自制”容器和上面的Just-Works™)
关于上述代码中转换的评论:将T
转换为C::value_type
的原因只是为了表明如果需要,这将是正确的位置。在上面的例子中,它可能看起来多余,但在我的真实世界代码中,我调用一个特殊的转换特征类来将e
s(编码字符串)转换为任何容器的正确值类型。
答案 1 :(得分:5)
大多数情况下,人们使用特征。
许多boost库已经解决了同样的问题,因此您可以重用现有的特征。
简单演示: 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, '?');
}
基本上,你所做的是,有一个独立的效用函数add_to
,它使用一个可以专门化的特征类traits::add_at_end
(在这种情况下适用于任何vector<...>
,{{ 1}}或set<...>
模板实例。
实际上,您可以通过继承常规实现来共享类似容器的实现(例如basic_string<...>
和deque
)。
答案 2 :(得分:2)
push_back
,std::string
和std::vector
支持 std::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;
}
};