Как вы можете перебирать элементы std::tuple?

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

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

но это не работает:

Ошибка 1: извините, не реализовано: невозможно развернуть "Listener ..." в список аргументов фиксированной длины.
Ошибка 2: я не могу появиться в постоянном выражении.

Итак, как правильно перебрать элементы кортежа?

24 ответа

Решение

Boost.Fusion - это возможность:

Непроверенный пример:

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

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

У меня есть ответ, основанный на итерации по кортежу:

#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 представлять что-то с соответствующими перегрузками для каждого типа, который может содержать кортеж. Это работает лучше всего, если вы знаете, что все элементы кортежа будут иметь общий базовый класс или что-то подобное.

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

[Онлайн пример на Колиру]

Это решение решает вопрос порядка оценки в ответе М. Алаггана.

C++ вводит операторы расширения для этой цели. Первоначально они были на пути к C++20, но чуть не попали в обрез из-за нехватки времени на пересмотр формулировок языка (см. Здесь и здесь).

В настоящее время согласованный синтаксис (см. Ссылки выше):

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

Более простой, интуитивно понятный и удобный для компилятора способ сделать это в 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, поэтому (я думаю) это более дружественно к компилятору.

В C++17 вы можете сделать это:

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

Это уже работает в Clang++ 3.9, используя std:: эксперимент::apply.

Используйте 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;
}

/questions/18355537/rasschitat-otnositelnoe-vremya-v-c/18355543#18355543

Вот простой 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>

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

Вам нужно использовать метапрограммирование шаблонов, показанное здесь с 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() в качестве переменной функции шаблона вместо.

Сначала определите несколько помощников индекса:

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

Другой вариант - реализовать итераторы для кортежей. Это имеет то преимущество, что вы можете использовать различные алгоритмы, предоставляемые стандартной библиотекой, и циклы for на основе диапазона. Элегантный подход к этому объясняется здесь https://foonathan.net/2017/03/tuple-iterator/. Основная идея - превратить кортежи в диапазон с помощьюbegin() а также end()методы для предоставления итераторов. Сам итератор возвращает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().

В MSVC STL есть функция _For_each_tuple_element (не документирована):

#include <tuple>

// ...

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

Если вы хотите использовать 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;
}

сила вариационных шаблонов!

С помощью 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);
  }
}

Другие упоминают о некоторых хорошо разработанных сторонних библиотеках, к которым вы можете обратиться. Однако, если вы используете 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. Кортеж может быть ссылочным типом или cv-квалифицированным;

  3. Поведение аналогично std::for_eachи возвращает ввод UnaryFunction;

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

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

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

Расширяя ответ @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;}
};

Кортеж boost предоставляет вспомогательные функции 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::tuple с gcc - он пока не принимает шаблоны переменных, но это может быть уже исправлено, так как на моей машине не установлена ​​последняя версия boost.

Из всех ответов, которые я видел здесь, здесь и здесь, мне больше всего понравился способ @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>()
    );
}

Демо: колиру

C++14-х std::make_index_sequence может быть реализовано для C++ 11.

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

#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);

    /* ... */
}

Там может быть место для улучшений.


Согласно коду OP, это станет следующим:

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

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

Выполнение Foreach со стилем (спорно)

      auto t = std::make_tuple(1, "two", 3.f);
t | foreach([](auto v){ std::cout << v << " "; });

И возвращаясь оттуда:

          auto t = std::make_tuple(1, "two", 3.f);
    auto sizes = t | foreach([](auto v) {
        return sizeof(v);
    });
    sizes | foreach([](auto v) {
        std::cout << v;
    });

Реализация (довольно простая)

Редактировать: становится немного грязнее.

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

      #include <utility>


// Optional includes, if you don't want to implement it by hand or google it
// you can find it in the repo (link below)
#include "typesystem/typelist.hpp"
// used to check if all return types are void, 
// making it a special case 
// (and, alas, not using constexpr-if 
//    for the sake of being compatible with C++14...) 


template <bool Cond, typename T, typename F>
using select = typename std::conditional<Cond, T, F>::type;


template <typename F>
struct elementwise_apply {
    F f;
};

template <typename F>
constexpr auto foreach(F && f) -> elementwise_apply<F> { return {std::forward<F>(f)}; }


template <typename R>
struct tuple_map {
    template <typename F, typename T, size_t... Is>
    static constexpr decltype(auto) impl(std::index_sequence<Is...>, F && f, T&& tuple) {
        return R{ std::forward<F>(f)( std::get<Is>(tuple) )... };
    }
};

template<>
struct tuple_map<void> {
    template <typename F, typename T, size_t... Is>
    static constexpr void impl(std::index_sequence<Is...>, F && f, T&& tuple) {
        [[maybe_unused]] std::initializer_list<int> _ {((void)std::forward<F>(f)( std::get<Is>(tuple) ), 0)... };
    }
};

template <typename F, typename... Ts>
constexpr decltype(auto) operator| (std::tuple<Ts...> & t, fmap<F> && op) {
    constexpr bool all_void = core::Types<decltype( std::move(op).f(std::declval<Ts&>()) )...>.all( core::is_void );
    using R = meta::select<all_void, void, std::tuple<decltype(std::move(op).f(std::declval<Ts&>()))...>>;
    return tuple_map<R>::impl(std::make_index_sequence<sizeof...(Ts)>{}, std::move(op).f, t);
}

template <typename F, typename... Ts>
constexpr decltype(auto) operator| (std::tuple<Ts...> const& t, fmap<F> && op) {
    constexpr bool all_void = check if all "decltype( std::move(op).f(std::declval<Ts>()) )..." types are void, since then it's a special case
    // e.g. core::Types<decltype( std::move(op).f(std::declval<Ts>()) )...>.all( core::is_void );
    using R = meta::select<all_void, void, std::tuple<decltype(std::move(op).f(std::declval<Ts const&>()))...>>;
    return tuple_map<R>::impl(std::make_index_sequence<sizeof...(Ts)>{}, std::move(op).f, t);
}

template <typename F, typename... Ts>
constexpr decltype(auto) operator| (std::tuple<Ts...> && t, fmap<F> && op) {
    constexpr bool all_void = core::Types<decltype( std::move(op).f(std::declval<Ts&&>()) )...>.all( core::is_void );
    using R = meta::select<all_void, void, std::tuple<decltype(std::move(op).f(std::declval<Ts&&>()))...>>;
    return tuple_map<R>::impl(std::make_index_sequence<sizeof...(Ts)>{}, std::move(op).f, std::move(t));
}

Да, было бы намного лучше, если бы мы использовали C++17.

Это также пример членов объекта std::moving, для которых лучше обратиться к этой хорошей краткой статье .

PS Если вы застряли, проверяя, являются ли все типы "decltype( std::move(op).f(std::declval()) )..." пустыми, вы можете найти какую-нибудь библиотеку метапрограммирования или, если эти библиотеки кажутся слишком сложно понять (некоторые из них могут быть связаны с какими-то сумасшедшими уловками метапрограммирования), вы знаете, где репозиторий githubискать

Вот решение, основанное наstd::interger_sequence.

Поскольку я не знаю, построен ли он изstd::make_tuple<T>(T &&...)в вашем коде. Это важно для того, как построитьstd::integer_sequenceв решении ниже.

(1) если у вас уже есть внешняя функция (не использующаяtemplate<typename ...T>), Вы можете использовать

      [](auto my_tuple)
{
    [&my_tuple]<typename N, N... n>(std::integer_sequence<N, n...> int_seq)
    {
        ((std::cout << std::get<n>(my_tuple) << '\n'), ...);
    }(std::make_index_sequence<std::tuple_size_v<decltype(my_tuple)>>{});
}(std::make_tuple());

(2) если вы еще не построилиmy_tupleв вашей функции и хотите обрабатывать вашиT ...arguments

      []<typename ...T>(T... args)
{
    [&args...]<typename N, N... n>(std::integer_sequence<N, n...> int_seq)
    {
        ((std::cout << std::get<n>(std::forward_as_tuple(args...)) << '\n'), ...);
    }(std::index_sequence_for<T...>{});
}();

Попробуйте использовать это:

      struct tuple_traits
{
private:
    template<size_t... I, typename T, typename FUNC>
    static constexpr void __handle(T&& tuple, FUNC&& func, std::index_sequence<I...>)
    {
        (func(std::get<I>(tuple)),...);
    }

public:
    template<typename T, typename FUNC>
    static constexpr void for_each(T&& tuple, FUNC&& func)
    {
        using TupleType = std::remove_reference_t<std::remove_cv_t<T>>;
        __handle(
            std::forward<T>(tuple), 
            std::forward<FUNC>(func), 
            std::make_index_sequence<std::tuple_size<TupleType>::value>{}
        );
    }
};

вы можете использовать это следующим образом:

      using custom_tuple = std::tuple<int, std::string>;

int main(int argc, char* argv[])
{
    custom_tuple _tuple{ 1, "123" };

    tuple_traits::for_each(
        _tuple,
        [](auto&& elem)
        {
            std::cout << elem << std::endl;
        }
    );

    return 0;
}
      template <typename F, typename T>
static constexpr size_t
foreach_in_tuple(std::tuple<T> & tuple, F && do_, size_t index_ = 0)
{
    do_(tuple, index_);
    return index_;
}

template <typename F, typename T, typename U, typename... Types>
static constexpr size_t
foreach_in_tuple(std::tuple<T,U,Types...> & tuple, F && do_, size_t index_ = 0)
{
    if(!do_(tuple, index_))
        return index_;
    auto & next_tuple = reinterpret_cast<std::tuple<U,Types...> &>(tuple);
    return foreach_in_tuple(next_tuple, std::forward<F>(do_), index_+1);
}


int main()
{
    using namespace std;
    auto tup = make_tuple(1, 2.3f, 'G', "hello");

    foreach_in_tuple(tup, [](auto & tuple, size_t i)
    {
        auto & value = std::get<0>(tuple);
        std::cout << i << " " << value << std::endl; 
        // if(i >= 2) return false; // break;
        return true; // continue
    });
}

Я наткнулся на ту же проблему для итерации по кортежу функциональных объектов, поэтому вот еще одно решение:

#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
Другие вопросы по тегам