从C ++ STL关联容器连接键/值

时间:2009-07-21 00:45:01

标签: c++ string stl join containers

我有一个对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_typep.second。我假设类似的定义适用于加入std::setstd::multiset键,但我没有任何需要。

我可以将这些函数应用于具有各种类型字符串的容器。对于键和值类型,mapmultimapstringwstring的任意组合的任意组合似乎都有效:

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<MapType>来调用它?
  3. 如何重构join_valuesjoin_keys函数和仿函数以避免重复大部分代码?
  4. 我确实找到了一个基于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>(","));
    

2 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分:2)

对于任何有兴趣的人,我都会根据keraba的意见找到以下解决方案。

我确实需要做一些改变,特别是:

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

  3. 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的设计更合适。