Як можна повторити елементи std :: tuple?


112

Як я можу повторити кортеж (за допомогою C ++ 11)? Я спробував таке:

for(int i=0; i<std::tuple_size<T...>::value; ++i) 
  std::get<i>(my_tuple).do_sth();

але це не працює:

Помилка 1: вибачте, не виконане: не можна розширити "Слухач ..." у список аргументів фіксованої довжини.
Помилка 2: я не можу відображатися в постійному виразі.

Отже, як я правильно перебираю елементи кортежу?


2
Чи можу я запитати, як ви компілюєте в C ++ 0x? Наскільки я не звільнений і не готовий.
Беркхард

5
g ++ містить експериментальну підтримку деяких функцій C ++ 0X, включаючи різні шаблони, починаючи з версії 4.3. Інші компілятори роблять те ж саме (з різними наборами функцій, якщо ви хочете використовувати їх у виробництві, ви повернетеся в 90-і з великою варіацією підтримки крайових речей)
AProgrammer

Я використовую g ++ версії 4.4 із std = c ++ 0x

9
Це питання потребує оновлення C ++ 11.
всезначний

2
@Omnifarious зараз, воно потребує оновлення
pepper_chico

Відповіді:


26

Boost.Fusion - це можливість:

Неперевірений приклад:

struct DoSomething
{
    template<typename T>
    void operator()(T& t) const
    {
        t.do_sth();
    }
};

tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());

@ViktorSehr AFAICT це не робить (принаймні, на GCC 4.7.2)? Хтось із підказкою?
sehe

@ViktorSehr Виявив проблему: помилка / упущення призводить до того, що поведінка Fusion залежить від порядку включення, див. Білет № 8418 для більш детальної інформації
1313

потрібно використовувати boost :: fusion :: tuple замість std :: tuple, щоб це працювало.
Марцін

У розділі GCC 8.1 / mingw-64 я отримую два попередження про використання boost :: fusion :: for_each з std лямбда-виразами: boost / mpl / assert.hpp: 188: 21: попередження: непотрібні дужки в декларації 'assert_arg' [-Wparentheses] не вдалося ************ (Pred :: ************ boost / mpl / assert.hpp: 193: 21: попередження: непотрібні дужки в декларація 'assert_not_arg' [-Wparentheses] не вдалося ************ (boost :: mpl :: not_ <Pred> :: ************
Hossein

129

У мене є відповідь, заснована на Ітерації над кортежем :

#include <tuple>
#include <utility> 
#include <iostream>

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  print(std::tuple<Tp...>& t)
  { }

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

int
main()
{
  typedef std::tuple<int, float, double> T;
  T t = std::make_tuple(2, 3.14159F, 2345.678);

  print(t);
}

Звичайна ідея - використовувати рекурсію часу компіляції. Насправді ця ідея використовується для створення printf, який є безпечним для типу, як зазначено в оригінальних картках.

Це можна легко узагальнити до а for_each кортежі для:

#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(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...>(t, f);
  }

Хоча для цього потрібні певні зусилля, щоб FuncTпредставити щось із відповідними перевантаженнями для кожного типу кортежу. Це найкраще працює, якщо ви знаєте, що всі елементи кортежу поділять загальний базовий клас або щось подібне.


5
Дякуємо за приємний простий приклад. Для початківців C ++, які шукають інформацію про те, як це працює, див. SFINAE та enable_ifдокументацію .
Faheem Mitha

Це можна легко узагальнити, щоб бути загальним for_each. Насправді я це робив сам. :-) Я думаю, що ця відповідь була б кориснішою, якби вона вже була узагальненою.
всезначний

4
Там я додав узагальнення, тому що мені насправді потрібен був, і я думаю, що було б корисно, щоб побачили інші.
всезначний

2
Примітка. Можливо, вам також знадобляться версії з const std::tuple<Tp...>&.. Якщо ви не збираєтесь змінювати кортежі під час ітерації, цих constверсій буде достатньо.
летальна гітара

2
Не так, як написано .. Ви можете зробити версію з індексуванням перевернутою - почніть з I = sizeof ... (Tp) і відлічіть. Потім явно надайте максимальну кількість аргументів. Ви також можете зробити версію, яка порушилась для типу тегу, скажімо, break_t. Тоді ви помістите об’єкт цього типу тегів у свій кортеж, коли ви хочете припинити друк. Або ви можете надати тип зупинки як парм-шаблон. Очевидно, ви не могли зламатись під час виконання.
emsr

55

У C ++ 17 ви можете використовувати std::applyзі виразом складки :

std::apply([](auto&&... args) {((/* args.dosomething() */), ...);}, the_tuple);

Повний приклад для друку кортежу:

#include <tuple>
#include <iostream>

int main()
{
    std::tuple t{42, 'a', 4.2}; // Another C++17 feature: class template argument deduction
    std::apply([](auto&&... args) {((std::cout << args << '\n'), ...);}, t);
}

[Інтернет-приклад про Coliru]

Це рішення вирішує питання порядку оцінки у відповіді М. Алагана .


1
Чи можете ви пояснити, що тут відбувається ((std::cout << args << '\n'), ...);? Лямбда викликається один раз з розпакованими кортежними елементами як args, але що з подвійними дужками?
helmesjo

4
@helmesjo Тут розширюється вираз із комою ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n')).
xskxzr

Зауважте, що у випадку, якщо ви хочете робити речі, які не є законними в кома-виразі (наприклад, декларування змінних і блоків), ви можете вкласти все це в метод і просто викликати його з виразу складеного кома-виразу.
Мірал

24

У C ++ 17 ви можете це зробити:

std::apply([](auto ...x){std::make_tuple(x.do_something()...);} , the_tuple);

Це вже працює в Clang ++ 3.9, використовуючи std :: експериментальний :: застосовувати.


4
Чи це не призводить до ітерації - тобто викликів do_something()-, що відбувається в не визначеному порядку, оскільки пакет параметрів розширюється в межах виклику функції (), де аргументи мають невказане впорядкування? Це може бути дуже важливим; Я думаю, що більшість людей очікують, що замовлення буде гарантовано в тому ж порядку, що і члени, тобто як індекси std::get<>(). AFAIK, щоб отримати гарантоване замовлення у подібних випадках, розширення необхідно зробити в межах {braces}. Я помиляюся? Ця відповідь робить акцент на такому впорядкуванні: stackoverflow.com/a/16387374/2757035
підкреслюйте_d

21

Використовуйте Boost.Hana та загальні лямбда:

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

struct Foo1 {
    int foo() const { return 42; }
};

struct Foo2 {
    int bar = 0;
    int foo() { bar = 24; return bar; }
};

int main() {
    using namespace std;
    using boost::hana::for_each;

    Foo1 foo1;
    Foo2 foo2;

    for_each(tie(foo1, foo2), [](auto &foo) {
        cout << foo.foo() << endl;
    });

    cout << "foo2.bar after mutation: " << foo2.bar << endl;
}

http://coliru.stacked-crooked.com/a/27b3691f55caf271


4
Будь ласка, будь ласка, не йдіть using namespace boost::fusion(особливо разом з using namespace std). Тепер немає жодного способу дізнатися, чи цеfor_each це std::for_eachабоboost::fusion::for_each
Bulletmagnet

3
@Bulletmagnet це було зроблено для терміновості тут, і ADL може вирішити це без проблем. Крім того, він також функціонує локально.
pepper_chico

16

C ++ представляє для цього оператори розширення . Вони спочатку були на шляху до C ++ 20, але вузько пропустили скорочення через брак часу на огляд формулювань мови (див. Тут і тут ).

На даний момент узгоджений синтаксис (див. Посилання вище):

{
    auto tup = std::make_tuple(0, 'a', 3.14);
    template for (auto elem : tup)
        std::cout << elem << std::endl;
}

15

Більш простий, інтуїтивний та зручний для компіляторів спосіб зробити це в C ++ 17, використовуючи if constexpr:

// prints every element of a tuple
template<size_t I = 0, typename... Tp>
void print(std::tuple<Tp...>& t) {
    std::cout << std::get<I>(t) << " ";
    // do things
    if constexpr(I+1 != sizeof...(Tp))
        print<I+1>(t);
}

Це рекурсія за часом компіляції, подібна до тієї, яку представив @emsr. Але це не використовує SFINAE, тому (я думаю) це більш зручно для компіляторів.


8

Вам потрібно використовувати метапрограмування шаблонів, показані тут з Boost.Tuple:

#include <boost/tuple/tuple.hpp>
#include <iostream>

template <typename T_Tuple, size_t size>
struct print_tuple_helper {
    static std::ostream & print( std::ostream & s, const T_Tuple & t ) {
        return print_tuple_helper<T_Tuple,size-1>::print( s, t ) << boost::get<size-1>( t );
    }
};

template <typename T_Tuple>
struct print_tuple_helper<T_Tuple,0> {
    static std::ostream & print( std::ostream & s, const T_Tuple & ) {
        return s;
    }
};

template <typename T_Tuple>
std::ostream & print_tuple( std::ostream & s, const T_Tuple & t ) {
    return print_tuple_helper<T_Tuple,boost::tuples::length<T_Tuple>::value>::print( s, t );
}

int main() {

    const boost::tuple<int,char,float,char,double> t( 0, ' ', 2.5f, '\n', 3.1416 );
    print_tuple( std::cout, t );

    return 0;
}

У C ++ 0x print_tuple()замість цього можна записати функцію варіативного шаблону.


8

Спочатку визначте кілька помічників індексу:

template <size_t ...I>
struct index_sequence {};

template <size_t N, size_t ...I>
struct make_index_sequence : public make_index_sequence<N - 1, N - 1, I...> {};

template <size_t ...I>
struct make_index_sequence<0, I...> : public index_sequence<I...> {};

За допомогою своєї функції ви хочете застосувати кожен елемент кортежу:

template <typename T>
/* ... */ foo(T t) { /* ... */ }

Ви можете написати:

template<typename ...T, size_t ...I>
/* ... */ do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) {
    std::tie(foo(std::get<I>(ts)) ...);
}

template <typename ...T>
/* ... */ do_foo(std::tuple<T...> &ts) {
    return do_foo_helper(ts, make_index_sequence<sizeof...(T)>());
}

Або якщо fooповертається void, використовуйте

std::tie((foo(std::get<I>(ts)), 1) ... );

Примітка: для C ++ 14 make_index_sequenceвже визначено ( http://en.cppreference.com/w/cpp/utility/integer_sequence ).

Якщо вам потрібно наказ про оцінку зліва направо, розгляньте щось подібне:

template <typename T, typename ...R>
void do_foo_iter(T t, R ...r) {
    foo(t);
    do_foo(r...);
}

void do_foo_iter() {}

template<typename ...T, size_t ...I>
void do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) {
    do_foo_iter(std::get<I>(ts) ...);
}

template <typename ...T>
void do_foo(std::tuple<T...> &ts) {
    do_foo_helper(ts, make_index_sequence<sizeof...(T)>());
}

1
Слід повернути значення повернення fooдо, voidперш ніж викликати, operator,щоб уникнути можливих патологічних перевантажень оператора.
Якк - Адам Невраумон

7

Ось простий C ++ 17 спосіб ітерації над кортежними елементами із просто стандартною бібліотекою:

#include <tuple>      // std::tuple
#include <functional> // std::invoke

template <
    size_t Index = 0, // start iteration at 0 index
    typename TTuple,  // the tuple type
    size_t Size =
        std::tuple_size_v<
            std::remove_reference_t<TTuple>>, // tuple size
    typename TCallable, // the callable to bo invoked for each tuple item
    typename... TArgs   // other arguments to be passed to the callable 
>
void for_each(TTuple&& tuple, TCallable&& callable, TArgs&&... args)
{
    if constexpr (Index < Size)
    {
        std::invoke(callable, args..., std::get<Index>(tuple));

        if constexpr (Index + 1 < Size)
            for_each<Index + 1>(
                std::forward<TTuple>(tuple),
                std::forward<TCallable>(callable),
                std::forward<TArgs>(args)...);
    }
}

Приклад:

#include <iostream>

int main()
{
    std::tuple<int, char> items{1, 'a'};
    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
    });
}

Вихід:

1
a

Це може бути розширено, щоб умовно перервати цикл у випадку, коли виклик повертає значення (але все-таки працювати з дзвінками, які не повертають присвоєне значення bool, наприклад, void):

#include <tuple>      // std::tuple
#include <functional> // std::invoke

template <
    size_t Index = 0, // start iteration at 0 index
    typename TTuple,  // the tuple type
    size_t Size =
    std::tuple_size_v<
    std::remove_reference_t<TTuple>>, // tuple size
    typename TCallable, // the callable to bo invoked for each tuple item
    typename... TArgs   // other arguments to be passed to the callable 
    >
    void for_each(TTuple&& tuple, TCallable&& callable, TArgs&&... args)
{
    if constexpr (Index < Size)
    {
        if constexpr (std::is_assignable_v<bool&, std::invoke_result_t<TCallable&&, TArgs&&..., decltype(std::get<Index>(tuple))>>)
        {
            if (!std::invoke(callable, args..., std::get<Index>(tuple)))
                return;
        }
        else
        {
            std::invoke(callable, args..., std::get<Index>(tuple));
        }

        if constexpr (Index + 1 < Size)
            for_each<Index + 1>(
                std::forward<TTuple>(tuple),
                std::forward<TCallable>(callable),
                std::forward<TArgs>(args)...);
    }
}

Приклад:

#include <iostream>

int main()
{
    std::tuple<int, char> items{ 1, 'a' };
    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
    });

    std::cout << "---\n";

    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
        return false;
    });
}

Вихід:

1
a
---
1

5

Якщо ви хочете використовувати std :: tuple і у вас є компілятор C ++, який підтримує різні шаблони, спробуйте внизу код (протестований з g ++ 4.5). Це має бути відповіддю на ваше запитання.

#include <tuple>

// ------------- UTILITY---------------
template<int...> struct index_tuple{}; 

template<int I, typename IndexTuple, typename... Types> 
struct make_indexes_impl; 

template<int I, int... Indexes, typename T, typename ... Types> 
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...> 
{ 
    typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type; 
}; 

template<int I, int... Indexes> 
struct make_indexes_impl<I, index_tuple<Indexes...> > 
{ 
    typedef index_tuple<Indexes...> type; 
}; 

template<typename ... Types> 
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...> 
{}; 

// ----------- FOR EACH -----------------
template<typename Func, typename Last>
void for_each_impl(Func&& f, Last&& last)
{
    f(last);
}

template<typename Func, typename First, typename ... Rest>
void for_each_impl(Func&& f, First&& first, Rest&&...rest) 
{
    f(first);
    for_each_impl( std::forward<Func>(f), rest...);
}

template<typename Func, int ... Indexes, typename ... Args>
void for_each_helper( Func&& f, index_tuple<Indexes...>, std::tuple<Args...>&& tup)
{
    for_each_impl( std::forward<Func>(f), std::forward<Args>(std::get<Indexes>(tup))...);
}

template<typename Func, typename ... Args>
void for_each( std::tuple<Args...>& tup, Func&& f)
{
   for_each_helper(std::forward<Func>(f), 
                   typename make_indexes<Args...>::type(), 
                   std::forward<std::tuple<Args...>>(tup) );
}

template<typename Func, typename ... Args>
void for_each( std::tuple<Args...>&& tup, Func&& f)
{
   for_each_helper(std::forward<Func>(f), 
                   typename make_indexes<Args...>::type(), 
                   std::forward<std::tuple<Args...>>(tup) );
}

boost :: fusion - це інший варіант, але для нього потрібен власний тип кортежу: boost :: fusion :: tuple. Давайте краще дотримуватись стандарту! Ось тест:

#include <iostream>

// ---------- FUNCTOR ----------
struct Functor 
{
    template<typename T>
    void operator()(T& t) const { std::cout << t << std::endl; }
};

int main()
{
    for_each( std::make_tuple(2, 0.6, 'c'), Functor() );
    return 0;
}

потужність різноманітних шаблонів!


Я спробував ваше перше рішення, але ця функція не справляється з парами. Будь-яка ідея, чому? Шаблон <typename T, typename U> void addt (пара <T, U> p) {cout << p.first + p.second << endl; } int main (int argc, char * argv []) {cout << "Привіт". << endl; for_each (make_tuple (2,3,4), [] (int i) {cout << i << endl;}); for_each (make_tuple (make_pair (1,2), make_pair (3,4)), addt); повернути 0; }
користувач2023370

Прикро, що ця відповідь написана так багатослівно, тому що я думаю, що спосіб ітерації (for_each_impl) є найелегантнішим із усіх рішень, які я бачив.
жокі

3

У MSVC STL є функція _For_each_tuple_element (не документально підтверджена):

#include <tuple>

// ...

std::tuple<int, char, float> values{};
std::_For_each_tuple_element(values, [](auto&& value)
{
    // process 'value'
});

2

Інші згадали про деякі добре розроблені сторонні бібліотеки, до яких ви можете звернутися. Однак якщо ви використовуєте C ++ без сторонніх бібліотек, наступний код може допомогти.

namespace detail {

template <class Tuple, std::size_t I, class = void>
struct for_each_in_tuple_helper {
  template <class UnaryFunction>
  static void apply(Tuple&& tp, UnaryFunction& f) {
    f(std::get<I>(std::forward<Tuple>(tp)));
    for_each_in_tuple_helper<Tuple, I + 1u>::apply(std::forward<Tuple>(tp), f);
  }
};

template <class Tuple, std::size_t I>
struct for_each_in_tuple_helper<Tuple, I, typename std::enable_if<
    I == std::tuple_size<typename std::decay<Tuple>::type>::value>::type> {
  template <class UnaryFunction>
  static void apply(Tuple&&, UnaryFunction&) {}
};

}  // namespace detail

template <class Tuple, class UnaryFunction>
UnaryFunction for_each_in_tuple(Tuple&& tp, UnaryFunction f) {
  detail::for_each_in_tuple_helper<Tuple, 0u>
      ::apply(std::forward<Tuple>(tp), f);
  return std::move(f);
}

Примітка: Код компілюється з будь-яким компілятором, що підтримує C ++ 11, і він підтримує узгодженість з дизайном стандартної бібліотеки:

  1. Кортежу не повинно бути std::tuple, а натомість може бути все, що підтримує std::getі std::tuple_size; зокрема, std::arrayі std::pairможе використовуватися;

  2. Кортеж може бути референтного типу або кваліфікованим відеоспостереженням;

  3. Він має подібну поведінку як std::for_eachі повертає вхід UnaryFunction;

  4. Для C ++ 14 (або Laster версія) користувачів, typename std::enable_if<T>::typeі typename std::decay<T>::typeможуть бути замінені на їх спрощеної версії, std::enable_if_t<T>і std::decay_t<T>;

  5. Для C ++ 17 (або Laster версії) користувачів, std::tuple_size<T>::valueможна замінити його спрощену версію std::tuple_size_v<T>.

  6. Для користувачів C ++ 20 (або останніх версій) SFINAEфункція може бути реалізована за допомогою Concepts.


2

Використовуючи constexprта if constexpr(C ++ 17), це досить просто і прямо:

template <std::size_t I = 0, typename ... Ts>
void print(std::tuple<Ts...> tup) {
  if constexpr (I == sizeof...(Ts)) {
    return;
  } else {
    std::cout << std::get<I>(tup) << ' ';
    print<I+1>(tup);
  }
}

1

Я, можливо, пропустив цей поїзд, але це буде тут для подальшого ознайомлення.
Ось моя конструкція, заснована на цій відповіді та на цій суті :

#include <tuple>
#include <utility>

template<std::size_t N>
struct tuple_functor
{
    template<typename T, typename F>
    static void run(std::size_t i, T&& t, F&& f)
    {
        const std::size_t I = (N - 1);
        switch(i)
        {
        case I:
            std::forward<F>(f)(std::get<I>(std::forward<T>(t)));
            break;

        default:
            tuple_functor<I>::run(i, std::forward<T>(t), std::forward<F>(f));
        }
    }
};

template<>
struct tuple_functor<0>
{
    template<typename T, typename F>
    static void run(std::size_t, T, F){}
};

Потім ви використовуєте його наступним чином:

template<typename... T>
void logger(std::string format, T... args) //behaves like C#'s String.Format()
{
    auto tp = std::forward_as_tuple(args...);
    auto fc = [](const auto& t){std::cout << t;};

    /* ... */

    std::size_t some_index = ...
    tuple_functor<sizeof...(T)>::run(some_index, tp, fc);

    /* ... */
}

Тут може бути місце для вдосконалень.


Відповідно до коду ОП, це стане таким:

const std::size_t num = sizeof...(T);
auto my_tuple = std::forward_as_tuple(t...);
auto do_sth = [](const auto& elem){/* ... */};
for(int i = 0; i < num; ++i)
    tuple_functor<num>::run(i, my_tuple, do_sth);

1

З усіх відповідей, які я бачив тут, тут і тут , мені сподобалось @sigidagi спосіб ітерації . На жаль, його відповідь є дуже багатослівною, що, на мою думку, затьмарює притаманну їй ясність.

Це моя версія його рішення, яка є більш стислою і працює std::tuple, std::pairі std::array.

template<typename UnaryFunction>
void invoke_with_arg(UnaryFunction)
{}

/**
 * Invoke the unary function with each of the arguments in turn.
 */
template<typename UnaryFunction, typename Arg0, typename... Args>
void invoke_with_arg(UnaryFunction f, Arg0&& a0, Args&&... as)
{
    f(std::forward<Arg0>(a0));
    invoke_with_arg(std::move(f), std::forward<Args>(as)...);
}

template<typename Tuple, typename UnaryFunction, std::size_t... Indices>
void for_each_helper(Tuple&& t, UnaryFunction f, std::index_sequence<Indices...>)
{
    using std::get;
    invoke_with_arg(std::move(f), get<Indices>(std::forward<Tuple>(t))...);
}

/**
 * Invoke the unary function for each of the elements of the tuple.
 */
template<typename Tuple, typename UnaryFunction>
void for_each(Tuple&& t, UnaryFunction f)
{
    using size = std::tuple_size<typename std::remove_reference<Tuple>::type>;
    for_each_helper(
        std::forward<Tuple>(t),
        std::move(f),
        std::make_index_sequence<size::value>()
    );
}

Демонстрація: coliru

C ++ 14 std::make_index_sequenceможе бути реалізовано для C ++ 11 .


0

Кортеж забезпечує піднімати торг допоміжних функцій get_head()і get_tail()тому ваші допоміжні функції можуть виглядати наступним чином :

inline void call_do_sth(const null_type&) {};

template <class H, class T>
inline void call_do_sth(cons<H, T>& x) { x.get_head().do_sth(); call_do_sth(x.get_tail()); }

як описано тут http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html

з std::tupleним має бути схожим.

Насправді, на жаль, std::tupleтакий інтерфейс не надає, тому методи, запропоновані раніше, повинні працювати, або вам доведеться перейти на boost::tupleякий має інші переваги (наприклад, оператори io вже надані). Хоча в boost::tuplegcc є і мінус - він ще не приймає варіативні шаблони, але це може бути вже виправлено, оскільки у мене на моїй машині не встановлена ​​остання версія прискорення.


0

Я натрапив на ту ж проблему, що стосується ітерації над набором об'єктів функцій, тож ось ще одне рішення:

#include <tuple> 
#include <iostream>

// Function objects
class A 
{
    public: 
        inline void operator()() const { std::cout << "A\n"; };
};

class B 
{
    public: 
        inline void operator()() const { std::cout << "B\n"; };
};

class C 
{
    public:
        inline void operator()() const { std::cout << "C\n"; };
};

class D 
{
    public:
        inline void operator()() const { std::cout << "D\n"; };
};


// Call iterator using recursion.
template<typename Fobjects, int N = 0> 
struct call_functors 
{
    static void apply(Fobjects const& funcs)
    {
        std::get<N>(funcs)(); 

        // Choose either the stopper or descend further,  
        // depending if N + 1 < size of the tuple. 
        using caller = std::conditional_t
        <
            N + 1 < std::tuple_size_v<Fobjects>,
            call_functors<Fobjects, N + 1>, 
            call_functors<Fobjects, -1>
        >;

        caller::apply(funcs); 
    }
};

// Stopper.
template<typename Fobjects> 
struct call_functors<Fobjects, -1>
{
    static void apply(Fobjects const& funcs)
    {
    }
};

// Call dispatch function.
template<typename Fobjects>
void call(Fobjects const& funcs)
{
    call_functors<Fobjects>::apply(funcs);
};


using namespace std; 

int main()
{
    using Tuple = tuple<A,B,C,D>; 

    Tuple functors = {A{}, B{}, C{}, D{}}; 

    call(functors); 

    return 0; 
}

Вихід:

A 
B 
C 
D

0

Іншим варіантом буде впровадження ітераторів для кортежів. Це має перевагу в тому, що ви можете використовувати різноманітні алгоритми, передбачені стандартною бібліотекою та на основі діапазону для циклів. Елегантний підхід до цього пояснюється тут https://foonathan.net/2017/03/tuple-iterator/ . Основна ідея полягає в тому, щоб перетворити кортежі в цілий ряд begin()і end()способи надання ітераторів. Сам ітератор повертає a, std::variant<...>який потім можна відвідати за допомогою std::visit.

Ось кілька прикладів:

auto t = std::tuple{ 1, 2.f, 3.0 };
auto r = to_range(t);

for(auto v : r)
{
    std::visit(unwrap([](auto& x)
        {
            x = 1;
        }), v);
}

std::for_each(begin(r), end(r), [](auto v)
    {
        std::visit(unwrap([](auto& x)
            {
                x = 0;
            }), v);
    });

std::accumulate(begin(r), end(r), 0.0, [](auto acc, auto v)
    {
        return acc + std::visit(unwrap([](auto& x)
        {
            return static_cast<double>(x);
        }), v);
    });

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(unwrap([](const auto& x)
        {
            std::cout << x << std::endl;
        }), v);
});

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(overload(
        [](int x) { std::cout << "int" << std::endl; },
        [](float x) { std::cout << "float" << std::endl; },
        [](double x) { std::cout << "double" << std::endl; }), v);
});

Моя реалізація (яка ґрунтується на поясненнях у посиланні вище):

#ifndef TUPLE_RANGE_H
#define TUPLE_RANGE_H

#include <utility>
#include <functional>
#include <variant>
#include <type_traits>

template<typename Accessor>
class tuple_iterator
{
public:
    tuple_iterator(Accessor acc, const int idx)
        : acc_(acc), index_(idx)
    {

    }

    tuple_iterator operator++()
    {
        ++index_;
        return *this;
    }

    template<typename T>
    bool operator ==(tuple_iterator<T> other)
    {
        return index_ == other.index();
    }

    template<typename T>
    bool operator !=(tuple_iterator<T> other)
    {
        return index_ != other.index();
    }

    auto operator*() { return std::invoke(acc_, index_); }

    [[nodiscard]] int index() const { return index_; }

private:
    const Accessor acc_;
    int index_;
};

template<bool IsConst, typename...Ts>
struct tuple_access
{
    using tuple_type = std::tuple<Ts...>;
    using tuple_ref = std::conditional_t<IsConst, const tuple_type&, tuple_type&>;

    template<typename T>
    using element_ref = std::conditional_t<IsConst,
        std::reference_wrapper<const T>,
        std::reference_wrapper<T>>;

    using variant_type = std::variant<element_ref<Ts>...>;
    using function_type = variant_type(*)(tuple_ref);
    using table_type = std::array<function_type, sizeof...(Ts)>;

private:
    template<size_t Index>
    static constexpr function_type create_accessor()
    {
        return { [](tuple_ref t) -> variant_type
        {
            if constexpr (IsConst)
                return std::cref(std::get<Index>(t));
            else
                return std::ref(std::get<Index>(t));
        } };
    }

    template<size_t...Is>
    static constexpr table_type create_table(std::index_sequence<Is...>)
    {
        return { create_accessor<Is>()... };
    }

public:
    static constexpr auto table = create_table(std::make_index_sequence<sizeof...(Ts)>{}); 
};

template<bool IsConst, typename...Ts>
class tuple_range
{
public:
    using tuple_access_type = tuple_access<IsConst, Ts...>;
    using tuple_ref = typename tuple_access_type::tuple_ref;

    static constexpr auto tuple_size = sizeof...(Ts);

    explicit tuple_range(tuple_ref tuple)
        : tuple_(tuple)
    {
    }

    [[nodiscard]] auto begin() const 
    { 
        return tuple_iterator{ create_accessor(), 0 };
    }

    [[nodiscard]] auto end() const 
    { 
        return tuple_iterator{ create_accessor(), tuple_size };
    }

private:
    tuple_ref tuple_;

    auto create_accessor() const
    { 
        return [this](int idx)
        {
            return std::invoke(tuple_access_type::table[idx], tuple_);
        };
    }
};

template<bool IsConst, typename...Ts>
auto begin(const tuple_range<IsConst, Ts...>& r)
{
    return r.begin();
}

template<bool IsConst, typename...Ts>
auto end(const tuple_range<IsConst, Ts...>& r)
{
    return r.end();
}

template <class ... Fs>
struct overload : Fs... {
    explicit overload(Fs&&... fs) : Fs{ fs }... {}
    using Fs::operator()...;

    template<class T>
    auto operator()(std::reference_wrapper<T> ref)
    {
        return (*this)(ref.get());
    }

    template<class T>
    auto operator()(std::reference_wrapper<const T> ref)
    {
        return (*this)(ref.get());
    }
};

template <class F>
struct unwrap : overload<F>
{
    explicit unwrap(F&& f) : overload<F>{ std::forward<F>(f) } {}
    using overload<F>::operator();
};

template<typename...Ts>
auto to_range(std::tuple<Ts...>& t)
{
    return tuple_range<false, Ts...>{t};
}

template<typename...Ts>
auto to_range(const std::tuple<Ts...>& t)
{
    return tuple_range<true, Ts...>{t};
}


#endif

Доступ лише для читання також підтримується переходом const std::tuple<>&до to_range().


0

Розширюючи відповідь на @Stypox, ми можемо зробити їх рішення більш загальним (C ++ 17 і далі). Додавши аргумент функції дзвінка:

template<size_t I = 0, typename... Tp, typename F>
void for_each_apply(std::tuple<Tp...>& t, F &&f) {
    f(std::get<I>(t));
    if constexpr(I+1 != sizeof...(Tp)) {
        for_each_apply<I+1>(t, std::forward<F>(f));
    }
}

Тоді нам потрібна стратегія для відвідування кожного типу.

Почнемо з деяких помічників (перші два взяті з cppreference):

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class ... Ts> struct variant_ref { using type = std::variant<std::reference_wrapper<Ts>...>; };

variant_ref використовується для зміни стану кортежів.

Використання:

std::tuple<Foo, Bar, Foo> tuples;

for_each_apply(tuples,
               [](variant_ref<Foo, Bar>::type &&v) {
                   std::visit(overloaded {
                       [](Foo &arg) { arg.foo(); },
                       [](Bar const &arg) { arg.bar(); },
                   }, v);
               });

Результат:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

Для повноти ось мої Bar& Foo:

struct Foo {
    void foo() {std::cout << "Foo" << i++ << std::endl;}
    int i = 0;
};
struct Bar {
    void bar() const {std::cout << "Bar" << std::endl;}
};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.