连接来自 C++ 的键/值 STL关联容器
我有一个对 STL 字符串进行操作的连接函数。 我希望能够将其应用于这样的容器:
getFoos(const std::multimap<std::string, std::string>& map) {
return join_values(",", map.equal_range("foo"));
换句话说,找到集合中所有匹配的键,并将这些值连接到带有给定分隔符的单个字符串中。 对于一系列键,lower_bound()
和 upper_bound()
也是如此,begin()
/end()
对于容器的全部内容等。
我能得到的最接近的是以下内容:(
template <typename T>
struct join_range_values : public T::const_iterator::value_type::second_type {
typedef typename T::const_iterator::value_type pair_type;
typedef typename pair_type::second_type value_type;
join_range_values(const value_type& sep) : sep(sep) { }
void operator()(const pair_type& p) {
// this function is actually more complex...
*this += sep;
*this += p.second;
}
private:
const value_type sep;
};
template <typename T>
typename T::const_iterator::value_type::second_type join_values(
const typename T::const_iterator::value_type::second_type& sep,
const std::pair<typename T::const_iterator, typename T::const_iterator>& range) {
return std::for_each(range.first, range.second, join_range_values<T>(sep));
}
我意识到从 std::string
继承或任何键/值类型通常被认为是坏主意,但我没有重载或重写任何函数,并且不需要虚拟析构函数,我这样做只是为了可以直接使用 for_each
的结果,而不必这样做。定义隐式转换运算符。)
join_range_keys
的定义非常相似,使用 first_type
和 p.first
代替 second_type< /code> 和
p.second
。 我假设类似的定义适用于连接 std::set
和 std::multiset
键,但我没有任何需要。
我可以将这些函数应用于具有各种类型字符串的容器。 map
和 multimap
的任意组合以及键和值类型的 string
和 wstring
的任意组合似乎都可以工作:
typedef std::multimap<std::string, std::string> NNMap;
const NNMap col;
const std::string a = join_keys<NNMap>(",", col.equal_range("foo"));
const std::string b = join_values<NNMap>(",", col.equal_range("foo"));
typedef std::multimap<std::string, std::wstring> NWMap;
const NWMap wcol;
const std::string c = join_keys<NWMap>(",", wcol.equal_range("foo"));
const std::wstring d = join_values<NWMap>(L",", wcol.equal_range("foo"));
typedef std::multimap<std::wstring, std::wstring> WWMap;
const WWMap wwcol;
const std::wstring e = join_keys<WWMap>(L",", wwcol.equal_range(L"foo"));
const std::wstring f = join_values<WWMap>(L",", wwcol.equal_range(L"foo"));
这给我留下了几个问题:
- 我是否缺少一些更简单的方法来完成同样的事情? 函数签名尤其显得过于复杂。
- 有没有办法让
join_values
自动推导模板参数类型,这样我就不需要每次都用join_values
调用它? - 如何重构
join_values
和join_keys
函数和函子以避免重复大部分代码?
我确实找到了一个基于 std::accumulate 的稍微简单的解决方案,但它似乎需要为范围内的每个元素对整个字符串进行两次完整的复制操作,因此效率要低得多我可以告诉。
template <typename T>
struct join_value_range_accum : public T::const_iterator::value_type::second_type
{
typedef typename T::const_iterator::value_type::second_type value_type;
join_value_range_accum(const value_type& sep) : sep(sep) {}
using value_type::operator=;
value_type operator+(const typename T::const_iterator::value_type& p)
{
return *this + sep + p.second;
}
private:
const value_type sep;
};
typedef std::multimap<std::string, std::string> Map;
Map::_Pairii range = map.equal_range("foo");
std::accumulate(range.first, range.second, join_value_range_accum<Map>(","));
I have a join function that operates on STL strings. I want to be able to apply it to to a container like this:
getFoos(const std::multimap<std::string, std::string>& map) {
return join_values(",", map.equal_range("foo"));
In other words, find all matching keys in the collection and concatenate the values into a single string with the given separator. Same thing with lower_bound()
and upper_bound()
for a range of keys, begin()
/end()
for the entire contents of the container, etc..
The closest I could get is the following:
template <typename T>
struct join_range_values : public T::const_iterator::value_type::second_type {
typedef typename T::const_iterator::value_type pair_type;
typedef typename pair_type::second_type value_type;
join_range_values(const value_type& sep) : sep(sep) { }
void operator()(const pair_type& p) {
// this function is actually more complex...
*this += sep;
*this += p.second;
}
private:
const value_type sep;
};
template <typename T>
typename T::const_iterator::value_type::second_type join_values(
const typename T::const_iterator::value_type::second_type& sep,
const std::pair<typename T::const_iterator, typename T::const_iterator>& range) {
return std::for_each(range.first, range.second, join_range_values<T>(sep));
}
(I realize that inheriting from std::string
or whatever the key/value types are is generally considered a bad idea, but I'm not overloading or overriding any functions, and I don't need a virtual destructor. I'm doing so only so that I can directly use the result of for_each
without having to define an implicit conversion operator.)
There are very similar definitions for join_range_keys
, using first_type
and p.first
in place of second_type
and p.second
. I'm assuming a similar definition will work for joining std::set
and std::multiset
keys, but I have not had any need for that.
I can apply these functions to containers with strings of various types. Any combination of map
and multimap
with any combination of string
and wstring
for the key and value types seems to work:
typedef std::multimap<std::string, std::string> NNMap;
const NNMap col;
const std::string a = join_keys<NNMap>(",", col.equal_range("foo"));
const std::string b = join_values<NNMap>(",", col.equal_range("foo"));
typedef std::multimap<std::string, std::wstring> NWMap;
const NWMap wcol;
const std::string c = join_keys<NWMap>(",", wcol.equal_range("foo"));
const std::wstring d = join_values<NWMap>(L",", wcol.equal_range("foo"));
typedef std::multimap<std::wstring, std::wstring> WWMap;
const WWMap wwcol;
const std::wstring e = join_keys<WWMap>(L",", wwcol.equal_range(L"foo"));
const std::wstring f = join_values<WWMap>(L",", wwcol.equal_range(L"foo"));
This leaves me with several questions:
- Am I missing some easier way to accomplish the same thing? The function signature especially seems overly complicated.
- Is there a way to have
join_values
automatically deduce the template parameter type so that I don't need to call it withjoin_values<MapType>
every time? - How can I refactor the
join_values
andjoin_keys
functions and functors to avoid duplicating most of the code?
I did find a slightly simpler solution based on std::accumulate
, but it seems to require two complete copy operations of the entire string for each element in the range, so it's much less efficient, as far as I can tell.
template <typename T>
struct join_value_range_accum : public T::const_iterator::value_type::second_type
{
typedef typename T::const_iterator::value_type::second_type value_type;
join_value_range_accum(const value_type& sep) : sep(sep) {}
using value_type::operator=;
value_type operator+(const typename T::const_iterator::value_type& p)
{
return *this + sep + p.second;
}
private:
const value_type sep;
};
typedef std::multimap<std::string, std::string> Map;
Map::_Pairii range = map.equal_range("foo");
std::accumulate(range.first, range.second, join_value_range_accum<Map>(","));
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
STL 算法通常使用迭代器,而不是容器,因此我建议如下所示。
然后,您需要一个迭代器来提取键或值。 这是一个例子:
如何使用:
The STL algorithms typically work with iterators, not containers, so I would suggest something like the following.
Then, you need an iterator that will pull out keys or values. Here's an example:
How to use:
仅供感兴趣的人参考,我根据 keraba 的输入得出了以下解决方案。
我确实必须进行一些更改,特别是:
T
模板参数设置为依赖类型名称,以便编译器自动推断它(允许带引号的文字自动转换为字符串对象)>const
因此不可修改。这允许:
以及:
基于
std::string
或std::wstring
的容器。这仍然相当复杂,但它解决了我原来帖子中的第 2 项和第 3 项,并且它似乎确实更适合 STL 的设计。
Just FYI for anyone interested, I came to the following solution based on keraba's input.
I did have to make some changes, specifically:
T
template parameter for the separator string a dependent type name so that the compiler would infer it automatically (allowing quoted literals to be automatically converted into string objects)const
, so a local temporary defined injoin()
ends up beingconst
and therefore unmodifiable.This allows for:
as well as:
with containers based on either
std::string
orstd::wstring
.This is still fairly complex, but it solves items 2 and 3 from my original post and it does seem to fit much better with the design of the STL.