Шаблонная функция map / multimap в C ++ - PullRequest
3 голосов
/ 08 июля 2010

Предположим, у меня есть:

std::map<K, V1> m1;
std::multimap<K, V2> m2;

Я хотел бы создать шаблон по типу контейнера и по типу ключа / значения. Однако следующее не работает: /

template <typename T>
void do_something(T var)
{
  // do something
}

template <typename TContainer, typename TKey, typename TVal>
void func(const TContainer<TKey, TVal>& container)
{
  for (typename TContainer<TKey, TVal>::iterator it = container.begin(); it != container.end(); ++it)
  {
    do_something(it->second);
  }
}

А затем назовите его с помощью:

func(m1);
func(m2);

Ответы [ 5 ]

7 голосов
/ 08 июля 2010

Разве вы не можете иметь только один параметр шаблона?

template <typename Container>
void func(const Container & container)
{
    for (typename Container::iterator it = container.begin(); it != container.end(); ++it)
    {
        do_something(it->second);
    }
}

Или лучше передать итераторы в вашу функцию вместо контейнера:

template <typename ForwardIterator>
void func(ForwardIterator begin, ForwardIterator end)
{
    for (; begin != end; ++begin)
    {
        do_something(begin->second);
    }
}

Если вы действительно хотите параметр шаблона шаблона, вот синтаксис:

template <template <typename, typename> Container, typename TKey, typename TValue>
void func(const Container<TKey, TValue> & container);

Однако, это не будет работать для контейнера STL, так как они обычно имеют больше параметров, чем кажется; на самом деле они часто имеют параметры со значениями по умолчанию, например, распределитель, поэтому лучше всего использовать описанный выше идиоматический способ написания универсального алгоритма, то есть иметь дело с итераторами вместо контейнеров.

7 голосов
/ 08 июля 2010

Причина, по которой это не работает, заключается в том, что std::map принимает четыре аргументы шаблона:

template<class Key,
         class Value,
         class Predicate = std::less<Key>,
         class Allocator = std::allocator<pair<const Key, Value> > >
class map;

Хотя вы можете опустить последние два параметра для создания экземпляров, вы должны перечислить их для соответствия шаблонов для работы:

template < typename TKey, 
           typename TVal, 
           class TPr, 
           class TAl
           template<typename,typename,class,class> TContainer >
void func(const TContainer<TKey, TVal, TPr, TAl>& container)
{
  for (typename TContainer<TKey, TVal, TPr, TAl>::iterator it = container.begin(); it != container.end(); ++it)
  {
    do_something(it->second);
  }
}

Тем не менее, мне интересно, почему вы беспокоитесь об этом. Идиоматическим способом было бы передать итераторы:

template <typename FwdIt>
void func(FwdIt begin, FwdIt end)
{
  while(begin != end) {
    do_something(begin->second);
    ++begin;
  }
}

Это также позволяет передавать все, что совместимо:

void f(const std::vector< std::pair<int, std::string> >& v)
{
   func( v.begin(), v.end() );
}
1 голос
/ 08 июля 2010

Передача итераторов, как предлагается несколько раз, является стандартным способом, но вы также можете использовать Boost.Range:

#include <boost/range.hpp>

template<typename ForwardReadableRange>
void func(const ForwardReadableRange& range) {
  typedef typename boost::range_iterator<const ForwardReadableRange>::type InputIterator;
  for (InputIterator it = boost::begin(range); it != boost::end(range); ++it) {
    do_something(it->second);
  }
}

template<typename ForwardReadableWriteableRange>
void func(ForwardReadableWriteableRange& range) {
  typedef typename boost::range_iterator<ForwardReadableWriteableRange>::type ForwardIterator;
  for (ForwardIterator it = boost::begin(range); it != boost::end(range); ++it) {
    do_something(it->second);
  }
}

Это позволяет вызывающей стороне передавать все, что моделирует ForwardReadable(Writeable)Range, например контейнеры илипары итераторов.

Конечно, это следует заменить на DoSomethingWithSecond функтор и for_each:

template<typename T, typename UnaryOp, typename Result>
struct DoSomethingWithSecond: std::unary_function<T, Result> {
  UnaryOp op;
  explicit DoSomethingWithSecond(UnaryOp op): op(op) { }
  Result operator()(T value) {
    return op(value.second);
  }
};
template<typename T>
void func(T range) {
  boost::for_each(range, DoSomethingWithSecond(do_something));
}
0 голосов
/ 08 июля 2010

Я бы определенно предложил следующий подход.Я не люблю создавать шаблоны для контейнера по многим причинам, поэтому давайте шаблон на итераторах.Громоздкий, но общий подход заключается в следующем:

#include <algorithm>
#include <functional>

// This should be part of the standard, but it isn't.
template <typename Func1, typename Func2>
struct composer :
    std::unary_function
        <
            typename Func2::argument_type,
            typename Func1::result_type
        >
{
    composer(Func1 f1_ = Func1(), Func2 f2_ = Func2())
        : f1(f1_), f2(f2_)
    {}

    typename Func1::result_type 
    operator()(typename Func2::argument_type x)
    { return f1(f2(x)); }

private:
    Func1 f1; Func2 f2;
};


template <typename F1, typename F2>
composer<F1, F2> compose(F1 f1, F2 f2)
{ return composer<F1, F2>(f1, f2); }


template <class C, typename T, T C::*ptr>
struct mem_ptr : std::unary_function<C&, T&>
{ T& operator()(C& x) { return x::*ptr; } };


template <typename Iter>
void func(Iter begin, Iter end)
{
    typedef typename Iter::value_type pair_t;

    typedef mem_ptr
    <
        pair_t,
        pair_t::second_type,
        pair_t::&second
    > second_of;

    std::for_each(begin, end, compose(ptr_fun(do_something), ptr_mem));
}

Функция полезности compose и класс mem_ptr - это вещи, которые должны были быть реализованы в стандарте (хотя они могут быть в TR1, хотя).Обратите внимание, что вы можете создавать шаблоны для типа do_something и даже передавать do_something в качестве аргумента.

Обратите внимание, что ptr_mem может быть улучшено до того, что вы можете назвать как

ptr_mem(pair_t::&second)

но это потребует еще немного кода.В Boost могут быть некоторые полезные вещи, но здесь мы можем довольствоваться подходом из 20 строк.

0 голосов
/ 08 июля 2010

Попробуйте, вам не нужно указывать, что контейнер настроен для ключей и типов значений (обратите внимание, что код одинаков для любого из этих типов), компилятор выдаст ошибку, если переданный тип неудовлетворить договор.В настоящее время контракт «имеет итератор», «имеет начало», «имеет конец» и «у итератора есть второй».

template <typename T>
void do_something(T var)  // Also consider "const T&"
{
  // do something
} 

template <typename TContainer>
void func(const TContainer& container)
{
  for (typename TContainer::iterator it = container.begin();
       it != container.end(); ++it)
  {
    do_something(it->second);
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...