web-dev-qa-db-ja.com

std :: tupleの要素をどのように反復できますか?

タプルを反復処理するにはどうすればよいですか(C++ 11を使用)?私は次を試しました:

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

しかし、これは機能しません:

エラー1:申し訳ありませんが、実装されていません:「リスナー...」を固定長の引数リストに展開できません。
エラー2:私は定数式に現れません。

だから、タプルの要素をどのように正しく反復するのですか?

86
1521237

Boost.Fusion は可能性です:

テストされていない例:

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

Tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());
22
Éric Malenfant

タプルの繰り返し に基づいた答えがあります:

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

通常のアイデアは、コンパイル時再帰を使用することです。実際、このアイデアは、元のTuple論文に記載されているように、タイプセーフな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がTupleに含まれる可能性のあるすべてのタイプに対して適切なオーバーロードを持つ何かを表すようにするための努力が必要ですが。これは、すべてのTuple要素が共通の基本クラスまたは類似のものを共有することがわかっている場合に最適に機能します。

120
emsr

C++ 17では、 std::apply with fold expression

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のオンライン例]

このソリューションは、 M。Alaggan's answer の評価順序の問題を解決します。

30
xskxzr

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

20
pepper_chico

C++ 17では、これを行うことができます。

std::apply([](auto ...x){std::make_Tuple(x.do_something()...);} , the_Tuple);

これは、std :: experimental :: applyを使用して、すでにClang ++ 3.9で機能します。

18
M. Alaggan

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()を可変長テンプレート関数として記述できます。

9

C++は、この目的のために 拡張ステートメント を導入しています。元々はC++ 20の軌道に乗っていましたが、言語の言葉遣いのレビューの時間が足りなかったため、カットをわずかに逃しました( here および here を参照)。

現在合意されている構文(上記のリンクを参照)は次のとおりです。

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

最初にいくつかのインデックスヘルパーを定義します。

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...> {};

関数を使用して、各Tuple要素に適用したい:

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

または、foovoidを返す場合、使用

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

標準ライブラリだけでタプルアイテムを反復処理する簡単な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

これは、callableが値を返す場合にループを条件付きで中断するように拡張できます(ただし、boolの割り当て可能な値を返さないcallableで動作します、たとえば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

if constexpr を使用して、C++ 17でこれを行う、よりシンプルで直感的でコンパイラーに優しい方法。

// 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を使用しないため、コンパイラーにとって使いやすいと思います。

5
Stypox

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は別のオプションですが、独自のTupleタイプ、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;
}

可変長テンプレートの力!

5
sigidagi

boostのTupleは、ヘルパー関数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はそのようなインターフェースを提供していないようです。そのため、前に提案したメソッドが機能するか、他の利点(既に提供されているio演算子など)を持つboost::Tupleに切り替える必要があります。 gccにはboost::Tupleの欠点がありますが、まだ可変長テンプレートを受け入れていませんが、最新バージョンのboostがマシンにインストールされていないため、すでに修正されている可能性があります。

1
Slava

herehere で見たすべての答えの中で、 @ sigidagi の最適な反復方法が好きでした。残念ながら、彼の答えは非常に冗長であり、私の意見では固有の明快さをあいまいにしています。

これは彼のソリューションの私のバージョンで、より簡潔でstd::Tuplestd::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>()
    );
}

デモ: colir

C++ 14のstd::make_index_sequenceを実装できます C++ 11の場合

1
joki

私はこの列車に乗り遅れたかもしれませんが、これは将来の参考のためにここにあります。
これは、この answer とこの Gist に基づいた構成です。

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

MSVC STLには、_For_each_Tuple_element関数があります(文書化されていません):

#include <Tuple>

// ...

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

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
Andreas DM

他の人は、適切に設計されたサードパーティのライブラリについて言及しています。ただし、サードパーティのライブラリなしで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::arraystd::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で実装できます。

0
M. W.

関数オブジェクトのタプルを反復処理する際に同じ問題に出くわしたので、もう1つの解決策を示します。

#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
tmaric