У меня есть функция соединения, которая работает со строками 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
или любого другого типа ключа / значения обычно считается плохой идеей, но я не перегружаю и не перезаписываю никакие функции, и мне не нужен виртуальный деструктор. I ' Я делаю это только для того, чтобы я мог напрямую использовать результат for_each
без необходимости определения оператора неявного преобразования.)
Существуют очень похожие определения для join_range_keys
, использующие first_type
и p.first
вместо second_type
и 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<MapType>
?
- Как я могу реорганизовать функции и функторы
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>(","));