2015-12-16 5 views
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'] (http://stackoverflow.com/q/1198260/1171191) – BoBTFish

+0

@LogicStuff Я не могу сделать все классы наследуемыми от чего-то с помощью' virtual void show() ', it также должен работать с '.size()' и std-контейнерами. – nwp

+2

Является ли списком время выполнения или время компиляции? вы всегда можете развернуть несколько вызовов для одной и той же функции [например, здесь] (http://coliru.stacked-crooked.com/a/7543103223c64c86) –

ответ

37

boost :: fusion is awesome, но oldskool - он удовлетворяет недостатки C++ 03.

C++ расширенное расширение шаблона 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 

другой, более общий способ (требуется C++ 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 подходит, но я могу изменить его, чтобы не использовать его, если это необходимо. Броски должны подавлять предупреждения компилятора в случае, если ваш функтор возвращает значение (которое тогда не используется) –

+0

Есть два лагеря: люди, которые используют броски C-стиля, и люди, которые этого не делают. Мне нравится второй лагерь, потому что он устраняет класс ошибок, вызванных плохим суждением, когда подходит C-стиль. –

15

основе https://stackoverflow.com/a/6894436/3484570 это работает без создания дополнительная функция, усиление или наследование.

Заголовок:

#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'. кажется, слишком много накладных расходов, чтобы копировать экземпляры только для их печати. [Демо] (http://coliru.stacked-crooked.com/a/cf0a4518d8f87de6) –

+1

@LorahAttkins Вы правы. К счастью, он также работает с 'std :: tie', поэтому копии можно избежать. Исправлена. – nwp

21

Другой вариант заключается в использовании 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(); } 
    ); 

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

+0

Это приятно знать. при установке apple clang 7.0 с -O3 компилятор вложил все в последовательность вызовов cout :: operator <<. то есть абсолютно нулевые служебные данные. Если boost это тоже, то это завещание фантастическим парням, которые поддерживают библиотеку. –

+0

@RichardHodges Согласен. Прост в использовании, портативен и так же быстро, как и не переносные решения :))) –

+0

Какой ответ не переносится? – ildjarn

54

В современном C++ вы бы использовать кратные выражения, чтобы «пройти через» ваши разнородные аргументы применения функции члена:

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

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

Demo

Вы можете прочитать подробнее об этом в моей blog

+5

примечание: «современный» здесь означает C++ 1z или лучше. –

+7

@RichardHodges Вы не стали более современными, чем это –

+0

Как это работает? Является ли лямбда-эквивалент функтору, содержащему функцию шаблона? – immibis

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', но у меня нет времени, чтобы найти его прямо сейчас. Если я найду его, я обновлю. –

+0

Может быть, это самый лучший и самый гибкий, но это использование выглядит слишком многословным. –

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(); 
} 

See it working online

+2

Это связано со стоимостью исполнения (возможно, вызовы девиртуализируются), требует модификации всех классов, и невозможно создать общий базовый класс для каждой функции. Кроме того, он не работает для buildins, переменных-членов и std-контейнеров с '.size()'. Но в целом вы правы, это традиционное решение. – nwp

+0

Для диспетчеризации времени выполнения, почему не просто 'std :: bind' функция-член вызывает в массив' std :: function '? Виртуального наследования не требуется. –

+0

@nwp: Он поставляется с крошечной крошечной крошечной микроскопической стоимостью, которая в основном исчезает внутри циклов или когда функция show нетривиальна. Ваше решение поставляется с оплатой стоимости. В бизнесе это во многих случаях более дорогостоящее решение, как во время программирования, так и при обслуживании. Все решения имеют свои преимущества и недостатки. Работает ли tie() еще раз, когда клиент хочет иметь гибкие пользовательские интерфейсы, например, как видели миллионы раз в типичных панелях мониторинга? –

4

Думаю, boost::variant стоит упомянуть. Тем более у него есть шансы стать std::variant в C++ 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. – ildjarn

+0

Как вы предлагаете использовать 'std :: tuple' здесь? – Mikhail

+0

'boost :: fusion :: for_each'. Я имею в виду, что если мы вносим Boost в любом случае, мы можем придерживаться самой подходящей структуры данных. : -] – ildjarn

0

Поздний ответ, но вот общее решение с C++ 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, они производят те же assembly output.

Смежные вопросы