3

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

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

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_valuesandjoin_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>(","));
4

2 回答 2

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()));
于 2009-07-21T02:53:22.637 回答
2

仅供任何有兴趣的人参考,我根据 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::string或的容器std::wstring

这仍然相当复杂,但它解决了我原始帖子中的第 2 项和第 3 项,并且它似乎更适合 STL 的设计。

于 2009-07-21T23:06:19.427 回答