连接来自 C++ 的键/值 STL关联容器

发布于 2024-07-29 11:08:24 字数 3575 浏览 5 评论 0原文

我有一个对 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_typep.first 代替 second_type< /code> 和 p.second。 我假设类似的定义适用于连接 std::setstd::multiset 键,但我没有任何需要。

我可以将这些函数应用于具有各种类型字符串的容器。 mapmultimap 的任意组合以及键和值类型的 stringwstring 的任意组合似乎都可以工作:

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"));

这给我留下了几个问题:

  1. 我是否缺少一些更简单的方法来完成同样的事情? 函数签名尤其显得过于复杂。
  2. 有没有办法让 join_values 自动推导模板参数类型,这样我就不需要每次都用 join_values 调用它?
  3. 如何重构 join_valuesjoin_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:

  1. Am I missing some easier way to accomplish the same thing? The function signature especially seems overly complicated.
  2. Is there a way to have join_values automatically deduce the template parameter type so that I don't need to call it with join_values<MapType> every time?
  3. How can I refactor the join_values and join_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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

爱*していゐ 2024-08-05 11:08:24

STL 算法通常使用迭代器,而不是容器,因此我建议如下所示。

template <typename T, typename Iterator>
T join(
    const T sep,
    Iterator b,
    Iterator e)
{
    T t;

    while (b != e)
        t = t + *b++ + sep;

    return t;
}

然后,您需要一个迭代器来提取键或值。 这是一个例子:

template <typename Key, typename Iterator>
struct KeyIterator
{
    KeyIterator(
        Iterator i)
        :_i(i)
    {
    }

    KeyIterator operator++()
    {
        ++_i;
        return *this;
    }

    bool operator==(
        KeyIterator ki)
    {
        return _i = ki._i;
    }

    typename Iterator::value_type operator*()
    {
        return _i->first;
    }
};

如何使用:

string s = join(",", KeyIterator(my_map.begin()), KeyIterator(my_map.end()));

The STL algorithms typically work with iterators, not containers, so I would suggest something like the following.

template <typename T, typename Iterator>
T join(
    const T sep,
    Iterator b,
    Iterator e)
{
    T t;

    while (b != e)
        t = t + *b++ + sep;

    return t;
}

Then, you need an iterator that will pull out keys or values. Here's an example:

template <typename Key, typename Iterator>
struct KeyIterator
{
    KeyIterator(
        Iterator i)
        :_i(i)
    {
    }

    KeyIterator operator++()
    {
        ++_i;
        return *this;
    }

    bool operator==(
        KeyIterator ki)
    {
        return _i = ki._i;
    }

    typename Iterator::value_type operator*()
    {
        return _i->first;
    }
};

How to use:

string s = join(",", KeyIterator(my_map.begin()), KeyIterator(my_map.end()));
ヅ她的身影、若隐若现 2024-08-05 11:08:24

仅供感兴趣的人参考,我根据 keraba 的输入得出了以下解决方案。

我确实必须进行一些更改,特别是:

  1. 将分隔符字符串的 T 模板参数设置为依赖类型名称,以便编译器自动推断它(允许带引号的文字自动转换为字符串对象)
  2. 使用从字符串派生的函子来解决迭代器中声明的依赖名称为 const 的事实,因此在 join() 中定义的本地临时变量最终会成为 >const 因此不可修改。

template <typename I>
struct MapKeyIterator : public I
{
    typedef typename I::value_type::first_type value_type;
    MapKeyIterator(I const &i) : I(i) { }
    value_type const & operator*() const { return (*this)->first; }
};

template <typename I>
struct MapValueIterator : public I
{
    typedef typename I::value_type::second_type value_type;
    MapValueIterator(I const &i) : I(i) { }
    value_type const & operator*() const { return (*this)->second; }
};

template <typename I>
struct join_functor : public I::value_type
{
    typedef typename I::value_type value_type;
    join_functor(value_type const &sep) : sep(sep) { }
    void operator()(value_type const &s)
    {
        *this += s;
        *this += sep;
    }
private:
    const value_type sep;
};

template <typename I>
typename I::value_type join(typename I::value_type const &sep, I beg, I const &end)
{
    return std::for_each(beg, end, join_functor<I>(sep));
}

template <typename I>
typename I::value_type::first_type join_keys(typename I::value_type::first_type const &sep, I const &beg, I const &end)
{
    return join(sep, MapKeyIterator<I>(beg), MapKeyIterator<I>(end));
}
template <typename I>
typename I::value_type::first_type join_keys(typename I::value_type::first_type const &sep, std::pair<I, I> const &ip)
{
    return join(sep, MapKeyIterator<I>(ip.first), MapKeyIterator<I>(ip.second));
}
template <typename I>
typename I::value_type::second_type join_values(typename I::value_type::second_type const &sep, I const &beg, I const &end)
{
    return join(sep, MapValueIterator<I>(beg), MapValueIterator<I>(end));
}
template <typename I>
typename I::value_type::second_type join_values(typename I::value_type::second_type const &sep, std::pair<I, I> const &ip)
{
    return join(sep, MapValueIterator<I>(ip.first), MapValueIterator<I>(ip.second));
}

这允许:

join_keys(",", map.equal_range("foo"));
join_values(",", map.equal_range("foo"));
join_values(",", map.begin(), map.end());

以及:

join(",", set.lower_bound("f"), set.upper_bound("g"));

基于 std::stringstd::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:

  1. Making the 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)
  2. Using a functor derived from string to get around the fact that the dependent name declared in the iterator is const, so a local temporary defined in join() ends up being const and therefore unmodifiable.

template <typename I>
struct MapKeyIterator : public I
{
    typedef typename I::value_type::first_type value_type;
    MapKeyIterator(I const &i) : I(i) { }
    value_type const & operator*() const { return (*this)->first; }
};

template <typename I>
struct MapValueIterator : public I
{
    typedef typename I::value_type::second_type value_type;
    MapValueIterator(I const &i) : I(i) { }
    value_type const & operator*() const { return (*this)->second; }
};

template <typename I>
struct join_functor : public I::value_type
{
    typedef typename I::value_type value_type;
    join_functor(value_type const &sep) : sep(sep) { }
    void operator()(value_type const &s)
    {
        *this += s;
        *this += sep;
    }
private:
    const value_type sep;
};

template <typename I>
typename I::value_type join(typename I::value_type const &sep, I beg, I const &end)
{
    return std::for_each(beg, end, join_functor<I>(sep));
}

template <typename I>
typename I::value_type::first_type join_keys(typename I::value_type::first_type const &sep, I const &beg, I const &end)
{
    return join(sep, MapKeyIterator<I>(beg), MapKeyIterator<I>(end));
}
template <typename I>
typename I::value_type::first_type join_keys(typename I::value_type::first_type const &sep, std::pair<I, I> const &ip)
{
    return join(sep, MapKeyIterator<I>(ip.first), MapKeyIterator<I>(ip.second));
}
template <typename I>
typename I::value_type::second_type join_values(typename I::value_type::second_type const &sep, I const &beg, I const &end)
{
    return join(sep, MapValueIterator<I>(beg), MapValueIterator<I>(end));
}
template <typename I>
typename I::value_type::second_type join_values(typename I::value_type::second_type const &sep, std::pair<I, I> const &ip)
{
    return join(sep, MapValueIterator<I>(ip.first), MapValueIterator<I>(ip.second));
}

This allows for:

join_keys(",", map.equal_range("foo"));
join_values(",", map.equal_range("foo"));
join_values(",", map.begin(), map.end());

as well as:

join(",", set.lower_bound("f"), set.upper_bound("g"));

with containers based on either std::string or std::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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文