Как перебирать коллекции в общем виде в C++?

проще говоря, если у меня есть набор и вектор, Как создать общий метод, который может обрабатывать оба параметра.

все, что я хочу сделать, это перебрать типы коллекций. Похоже, это должно быть тривиально, но я что-то упускаю.

void printMeSomeStrings(somebaseclass<string> strings) {
  for (auto& str : strings) {
    cout << str << endl;
  }
}

В C# я бы прошел IEnumerable или что-то в этом роде. Тогда я мог бы повторить всю коллекцию.

общие читая объяснения ответ будут оценены.

4 ответов


первый вариант-поместить код, выполняющий итерацию в шаблон. Это требует разоблачения реализации для всех, кто ее использует, что имеет недостатки.

в основном, возьмите тип C как template параметр, затем напишите свой код в терминах этого типа C.

template<typename C>
void printMeSomeStrings(C&& strings) {
  for (auto const& str : strings) {
    cout << str << endl;
  }
}

если вы хотите иметь сильный барьер между интерфейсом и реализацией, подход C++11 будет заключаться в стирании типа на for-iterable контейнер, а затем выставить for-iterable контейнер, как std::function строительство.

это сложнее. Я лично нахожу написание for_each функция проще, чем писать полномасштабный адаптер итерации. Если вам нужен полномасштабный объект итерации типа контейнера erasure, начните с boost, или спросите меня ниже, и я могу это сделать.

на for_each адаптер легко, однако.

#include <functional>
#include <utility>
#include <iterator>
#include <memory>

template<typename T>
struct for_each_helper_interface {
  virtual ~for_each_helper_interface() {}
  virtual void for_each( std::function< void(T) > const& ) = 0;
};
template<typename C, typename T>
struct for_each_helper:for_each_helper_interface<T> {
  C& c;
  for_each_helper( C& in ):c(in) {}
  virtual void for_each( std::function< void(T) > const& f ) override final {
    for( auto&& x:c ) {
      f(x);
    }
  }
};
template<typename T>
struct for_each_adaptor {
  std::unique_ptr<for_each_helper_interface<T>> pImpl;
  void for_each( std::function< void(T) > const& f ) {
    if (pImpl) {
      pImpl->for_each(f);
    }
  }
  template<typename C>
  for_each_adaptor( C&& c ): pImpl( new for_each_helper<C, T>( std::forward<C>(c) ) ) {}
};

который будет печатать-стереть контейнер T (или типа конвертируемый в T!) и разоблачить for_each метод, который позволяет просматривать содержимое контейнера. Используйте так:

#include <set>
#include <iostream>
#include <vector>
void print_stufF( for_each_adaptor<std::string const&> c ) {
  c.for_each([&](std::string const&s){
    std::cout << s << "\n";
  });
}
int main() {
   std::set<std::string> s;
   s.insert("hello");
   s.insert("world");
   print_stuff(s);
   std::vector<std::string> v;
   v.push_back("hola");
   v.push_back("bola");
   print_stuff(v);
 }

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

это означает, что специалист std::begin или определяет свое собственное начало не нужно связанный: вместо этого мы создаем специальные отношения в точке использования.

живой пример:http://ideone.com/xOqBkI


вы можете использовать шаблоны. Например:

#include <iostream>

template<typename C>
void foo(C const& c)
{
    std::cout << "{ ";
    for (auto const& x : c)
    {
        std::cout << x << " ";
    }
    std::cout << "}";
}

и вот как вы его используете:

#include <set>
#include <vector>

int main()
{
    std::vector<int> v = {1, 2, 3};
    foo(v);

    std::cout << std::endl;

    std::set<std::string> s = {"Hello,", "Generic", "World!"};
    foo(s);
}

видео.


Это ровно для чего были разработаны итераторы.

template <class It>
void print_some_strings(It first, It last) {
    std::cout << *first++ << std::endl;
}

в C# я бы прошел IEnumerable или что-то в этом роде.

C++ использует более питонический подход duck typing для определения интерфейсов (обычно называемый концептом на C++), а не с помощью наследования. Чтобы сделать утку ввода в C++, вы используете функцию шаблона, как это:

template<typename C>
void printMeSomeStrings(const C& strings) 
{
    for (const auto& str : strings) 
    {
        cout << str << endl;
    }
}

в python ввод утки выполняется во время выполнения, но в C++ это делается во время компиляции, поэтому нет затрат времени выполнения для ввода утки, и все проверяется на время компиляции также.

вот дополнительная информация о C++, чтобы помочь в поиске информации. Во-первых, эквивалент IEnumerator<T> является итератором в C++. здесь - это страница о различных категориях итераторов и о том, что необходимо реализовать для итераторов. По устаревшим причинам итераторы моделируются после указателей в C, что позволяет использовать массивы C со стандартными алгоритмами c++.

однако, в отличие от IEnumerator<T> итераторы должны быть парными. - итератор до начала и конца (который является одним из последних элементов). Итак, эквивалент IEnumerable<T> В C++ называется диапазон. В C++11, диапазон определяется двумя свободными функциями, begin(T) и end(T)(Он также может быть реализован как функция-член .begin() и .end()).

определив концепцию (интерфейс aka) как две свободные функции, в отличие от использования наследования, диапазоны могут быть реализованы неинтрузивно. Например, если у вас есть работа с некоторым устаревшим api, который использует C стиль связанных списков. Теперь их можно адаптировать как диапазон c++11 и использовать внутри цикла C++ for.