Итерации по разным типам

60

С учетом следующего кода:

struct Window{
    void show();
    //stuff
}w1, w2, w3;

struct Widget{
    void show();
    //stuff
}w4, w5, w6;

struct Toolbar{
    void show();
    //stuff
}t1, t2, t3;

Я хочу show добавить несколько элементов:

for (auto &obj : {w3, w4, w5, t1})
    obj.show();

Однако это не скомпилируется, так как std::initializer_list<T> в for -loop не может вывести T, и на самом деле нет подходящего T. Я не хочу создавать тип стирания типа из-за количества требуемого кода и ненужных служебных данных во время выполнения. Как правильно написать мой цикл так, чтобы тип obj выводился для каждого элемента в концептуальном списке отдельно?

  • 9
    Используйте tuple
  • 0
    @LogicStuff Я не могу заставить все классы наследовать от чего-либо с помощью virtual void show() , он также должен работать с .size() и std-контейнерами.
Показать ещё 6 комментариев
Теги:
c++11
c++14

8 ответов

52

В современном С++ вы должны использовать fold выражения, чтобы "пропустить" ваши гетерогенные аргументы, применяя функцию-член:

auto Printer = [](auto&&... args) {
    (args.show(), ...);
};

Printer(w1, w2, w3, w4, w5, w6, t1, t2, t3);

Демо

Подробнее об этом читайте в блоге

  • 5
    примечание: «современный» здесь означает c ++ 1z или лучше.
  • 7
    @RichardHodges Вы не станете более современными, чем это
Показать ещё 4 комментария
37

boost:: fusion - это потрясающе, но oldskool - он удовлетворяет недостатки С++ 03.

С++ 11 расширение вариационного шаблона для спасения!

#include <iostream>

struct Window{
    void show() {
        std::cout << "Window\n";
    }
    //stuff
}w1, w2, w3;

struct Widget{
    void show() {
        std::cout << "Widget\n";
    }
    //stuff
}w4, w5, w6;

struct Toolbar{
    void show()
    {
        std::cout << "Toolbar\n";
    }
    //stuff
}t1, t2, t3;


template<class...Objects>
void call_show(Objects&&...objects)
{
    using expand = int[];
    (void) expand { 0, ((void)objects.show(), 0)... };
}

auto main() -> int
{
    call_show(w3, w4, w5, t1);
    return 0;
}

ожидаемый вывод:

Window
Widget
Widget
Toolbar

другой, более общий путь (требуется С++ 14):

// note that i have avoided a function names that look like
// one in the standard library.

template<class Functor, class...Objects>
void for_all(Functor&& f, Objects&&... objects)
{
    using expand = int[];
    (void) expand { 0, (f(std::forward<Objects>(objects)), 0)... };

}

называется так:

for_all([](auto& thing) { thing.show(); }, w3, w4, w5, t1);
  • 2
    Забавно, что вы думаете о boost::fusion oldskool, но при этом вы используете приведение в стиле C. Двойные стандарты?
  • 5
    @MaximEgorushkin :) это один из немногих случаев, когда уместно приведение в стиле c, но я могу изменить его, чтобы он не использовался при необходимости. Приведения приводятся для подавления предупреждений компилятора в случае, если ваш функтор возвращает значение (которое затем не используется)
Показать ещё 6 комментариев
20

Другой вариант - использовать алгоритмы boost::tuple или std::tuple и boost::fusion::for_each:

#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/adapted/boost_tuple.hpp>

boost::fusion::for_each(
    boost::tie(w1, w2, w3, w4, w5, w6, t1, t2, t3), // by reference, not a copy
    [](auto&& t) { t.show(); } 
    );

Просто из любопытства сравнил сгенерированный сборник метода Ричарда Ходжеса с приведенным выше. С gcc-4.9.2 -Wall -Wextra -std=gnu++14 -O3 -march=native полученный код сборки идентичен.

  • 0
    Это приятно знать. в моей установке apple clang 7.0 с -O3 компилятор включил все в серию вызовов cout :: operator <<. т.е. абсолютно нулевые накладные расходы. Если повышение делает это тоже, это является свидетельством фантастических парней, которые поддерживают библиотеку.
  • 0
    @RichardHodges Я согласен. Простая в использовании, портативная и быстрая, как непереносимые решения :)))
Показать ещё 3 комментария
15

На основе https://stackoverflow.com/questions/1198260/iterate-over-tuple это работает без создания дополнительной функции, повышения или наследования.

Заголовок:

#include <tuple>
#include <utility> 

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_each(const std::tuple<Tp...> &, FuncT) // Unused arguments are given no names.
  { }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_each(const std::tuple<Tp...>& t, FuncT f)
  {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(t, f);
  }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...> &&, FuncT) // Unused arguments are given no names.
  { }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...>&& t, FuncT f)
  {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(std::move(t), f);
  }

.cpp

struct Window{
    void show(){}
    //stuff
}w1, w2, w3;

struct Widget{
    void show(){}
    //stuff
}w4, w5, w6;

struct Toolbar{
    void show(){}
    //stuff
}t1, t2, t3;

int main() {
    for_each(std::tie(w3, w4, w5, t1), [](auto &obj){
        obj.show();
    });
}
  • 3
    вы копируете w3, w4, w5, t1 при вызове make_tuple . кажется, что копировать экземпляры просто для их печати - это слишком много. демонстрация
  • 1
    @LorahAttkins Вы правы. К счастью, он также работает с std::tie , поэтому копий можно избежать. Исправлена.
11

Window, Widget и Toolbar обмениваются общим интерфейсом, поэтому вы можете создавать абстрактный класс и наследовать от него другие классы:

struct Showable {
    virtual void show() = 0; // abstract method
};

struct Window: Showable{
    void show();
    //stuff
}w1, w2, w3;

struct Widget: Showable{
    void show();
    //stuff
}w4, w5, w6;

struct Toolbar: Showable{
    void show();
    //stuff
}t1, t2, t3;

Затем вы можете создать массив указателей на Showable и перебрать его:

int main() {
    Showable *items[] = {&w3, &w4, &w5, &t1};
    for (auto &obj : items)
        obj->show();
}

Посмотрите, как он работает в Интернете

  • 3
    Это связано с затратами времени выполнения (возможно, вызовы становятся девиртуализированными), требует модификации всех классов, и невозможно создать общий базовый класс для каждой функции. Кроме того, он не работает для buildins, переменных-членов и std-контейнеров с .size() . Но в целом вы правы, это традиционное решение.
  • 0
    Для диспетчеризации во время выполнения, почему бы просто не std::bind вызвать функции-члены в массив std::function<void()> ? Нет виртуального наследования на виджете не требуется.
Показать ещё 3 комментария
8

Я рекомендую Boost.Hana, в котором ИМХО является лучшей и самой гибкой библиотекой мета-программирования шаблонов.

#include <boost/hana/ext/std/tuple.hpp>
#include <boost/hana.hpp>

namespace hana = boost::hana;

hana::for_each(std::tie(w3, w4, w5, t1), [](auto& obj) { obj.show(); });
  • 1
    стыдно за все эти вызовы std :: ref () ... так много печатать ...
  • 0
    @Richard В библиотеке может быть эквивалент std::tie , но у меня нет времени, чтобы найти его прямо сейчас. Если я найду один, я обновлю.
Показать ещё 11 комментариев
4

Я думаю, что boost::variant стоит упомянуть. Тем более, что он имеет шансы стать std::variant в С++ 17.

int main()
{
  std::vector<boost::variant<Window*, Widget*, Toolbar*>> items = { &w1, &w4, &t1 };

  for (const auto& item : items)
  {
    boost::apply_visitor([](auto* v) { v->show(); }, item);
  }
  return 0;
}
  • 0
    Я люблю варианты, но это просто ненужная косвенность. std::tuple уже находится в C ++ 11.
  • 0
    Как вы предлагаете использовать std::tuple здесь?
Показать ещё 2 комментария
0

Поздний ответ, но здесь общее решение с С++ 14, которое работает как boost::fusion::for_each но не требует увеличения:

#include <tuple>

namespace detail {
template<typename Tuple, typename Function, std::size_t... Is>
void tuple_for_each_impl(Tuple&& tup, Function&& fn, std::index_sequence<Is...>) {
    using dummy = int[];
    static_cast<void>(dummy {
        0, (static_cast<void>(fn(std::get<Is>(std::forward<Tuple>(tup)))), 0)...
    });
}
}

template<typename Function, typename... Args>
void tuple_for_each(std::tuple<Args...>&& tup, Function&& fn) {
    detail::tuple_for_each_impl(std::forward<std::tuple<Args...>>(tup),
            std::forward<Function>(fn), std::index_sequence_for<Args...>{});
}

int main() {
    tuple_for_each(std::tie(w1, w2, w3, w4, w5, w6, t1, t2, t3), [](auto&& arg) {
        arg.show();
    });
}

Если вы хотите достичь более или менее того же уровня без std::tuple, вы можете создать однофункциональный вариант над кодом:

#include <utility>

template<typename Function, typename... Args>
void va_for_each(Function&& fn, Args&&... args) {
    using dummy = int[];
    static_cast<void>(dummy {
        0, (static_cast<void>(fn(std::forward<Args>(args))), 0)...
    });
}

int main() {
    auto action = [](auto&& arg) { arg.show(); };
    va_for_each(action, w1, w2, w3, w4, w5, w6, t1, t2, t3);
}

Недостатком второго примера является то, что он требует сначала указать функцию обработки, поэтому не имеет такого же вида, как хорошо известный std::for_each. В любом случае с моим компилятором (GCC 5.4.0) с использованием уровня оптимизации -O2 они создают тот же сборку.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню