Bagaimana Anda bisa mengulang elemen dari std :: tuple?

112

Bagaimana saya bisa melakukan iterasi melalui tupel (menggunakan C ++ 11)? Saya mencoba yang berikut ini:

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

tapi ini tidak berhasil:

Kesalahan 1: maaf, tidak diterapkan: tidak dapat memperluas 'Pendengar ...' menjadi daftar argumen dengan panjang tetap.
Kesalahan 2: Saya tidak dapat muncul dalam ekspresi konstan.

Jadi, bagaimana cara saya mengulang dengan benar elemen tupel?

einpoklum
sumber
2
Bolehkah saya bertanya, bagaimana Anda mengkompilasi di C ++ 0x? Sejauh yang saya tahu, itu belum dirilis atau siap.
Burkhard
5
g ++ berisi dukungan eksperimental untuk beberapa fitur C ++ 0X, termasuk template variadic, sejak versi 4.3. Kompiler lain melakukan hal yang sama (dengan set fitur yang berbeda, jika Anda ingin menggunakannya dalam produksi, Anda kembali ke 90 dengan variasi dukungan yang luas untuk hal-hal yang paling baru)
Pemrogram
Saya menggunakan g ++ versi 4.4 dengan std = c ++ 0x
9
Pertanyaan ini membutuhkan pembaruan C ++ 11.
Omnifarious
2
@Omnifarious sekarang, perlu pembaruan C ++ 14
pepper_chico

Jawaban:

26

Boost.Fusion adalah kemungkinan:

Contoh yang belum teruji:

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

tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());
Éric Malenfant
sumber
@ViktorSehr AFAICT tidak (setidaknya di GCC 4.7.2)? Ada yang punya petunjuk?
lihat
@ViktorSehr Menemukan masalah: bug / kelalaian menyebabkan perilaku fusi bergantung pada urutan
penyertaan
perlu menggunakan boost :: fusion :: tuple daripada std :: tuple agar ini berfungsi.
Marcin
Di bawah GCC 8.1 / mingw-64, saya mendapatkan dua peringatan untuk penggunaan boost :: fusion :: for_each dengan ekspresi lambda std: boost / mpl / assert.hpp: 188: 21: peringatan: tanda kurung yang tidak perlu dalam deklarasi 'assert_arg' [-Wparentheses] gagal ************ (Pred :: ************ boost / mpl / assert.hpp: 193: 21: peringatan: tanda kurung yang tidak perlu di deklarasi 'assert_not_arg' [-Wparentheses] gagal ************ (boost :: mpl :: not_ <Pred> :: ************
Hossein
129

Saya memiliki jawaban berdasarkan Iterasi atas Tuple :

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

Ide yang biasa adalah menggunakan rekursi waktu kompilasi. Sebenarnya ide ini digunakan untuk membuat printf yang bertipe safe seperti yang tertera pada kertas tuple original.

Ini dapat dengan mudah digeneralisasikan menjadi for_eachuntuk tupel:

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

Meskipun ini kemudian membutuhkan usaha untuk FuncTmerepresentasikan sesuatu dengan kelebihan beban yang sesuai untuk setiap jenis yang mungkin berisi tupel. Ini berfungsi paling baik jika Anda tahu semua elemen tupel akan berbagi kelas dasar yang sama atau yang serupa.

emsr
sumber
5
Terima kasih atas contoh sederhana yang bagus. Untuk pemula C ++ yang mencari latar belakang tentang cara kerjanya, lihat SFINAE dan enable_ifdokumentasi .
Faheem Mitha
Ini dapat dengan mudah digeneralisasikan menjadi generik for_each. Faktanya, saya melakukannya sendiri. :-) Saya rasa jawaban ini akan lebih berguna jika sudah digeneralisasikan.
Omnifarious
4
Di sana, saya menambahkan generalisasi karena saya benar-benar membutuhkannya, dan menurut saya akan berguna bagi orang lain untuk melihatnya.
Omnifarious
2
Catatan: Anda mungkin juga memerlukan versi dengan const std::tuple<Tp...>&.. Jika Anda tidak bermaksud untuk memodifikasi tupel saat melakukan iterasi, constversi tersebut sudah cukup.
lethal-guitar
2
Tidak seperti yang tertulis .. Anda bisa membuat versi dengan pengindeksan terbalik - mulai dari I = sizeof ... (Tp) dan hitung mundur. Kemudian berikan jumlah argumen maksimum secara eksplisit. Anda juga dapat membuat versi yang melanggar jenis tag, misalnya break_t. Kemudian Anda akan meletakkan objek dari jenis tag itu di tupel Anda ketika Anda ingin berhenti mencetak. Atau Anda bisa menyediakan tipe stop sebagai template parm. Jelas Anda tidak bisa istirahat pada saat berjalan.
emsr
55

Di C ++ 17, Anda dapat menggunakan std::applydengan ekspresi lipat :

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

Contoh lengkap untuk mencetak tupel:

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

[Contoh Online di Coliru]

Solusi ini memecahkan masalah urutan evaluasi dalam jawaban M. Alaggan .

xskxzr.dll
sumber
1
Bisakah Anda menjelaskan apa yang terjadi di sini: ((std::cout << args << '\n'), ...);? Lambda dipanggil sekali dengan tuple-elements yang dibuka sebagai args, tapi ada apa dengan tanda kurung ganda?
helmesjo
4
@helmesjo Ini diperluas menjadi ekspresi koma di ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n'))sini.
xskxzr
Perhatikan bahwa jika Anda ingin melakukan hal-hal yang tidak legal dalam ekspresi koma (seperti mendeklarasikan variabel dan blok), Anda dapat memasukkan semua itu ke dalam metode dan cukup memanggilnya dari dalam ekspresi koma-lipat.
Miral
24

Di C ++ 17 Anda dapat melakukan ini:

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

Ini sudah berfungsi di Clang ++ 3.9, menggunakan std :: eksperimental :: apply.

Mohammad Alaggan
sumber
4
Tidakkah ini mengarah pada iterasi - yaitu panggilan do_something()- terjadi dalam urutan yang tidak ditentukan, karena paket parameter diperluas dalam panggilan fungsi (), di mana argumen memiliki urutan yang tidak ditentukan? Itu mungkin sangat penting; Saya membayangkan kebanyakan orang akan mengharapkan pemesanan dijamin untuk terjadi dalam urutan yang sama dengan anggota, yaitu sebagai indeks std::get<>(). AFAIK, untuk mendapat jaminan pemesanan dalam kasus seperti ini, perluasan harus dilakukan di dalam {braces}. Apakah aku salah? Jawaban ini menekankan pada pengurutan tersebut: stackoverflow.com/a/16387374/2757035
underscore_d
21

Gunakan Boost.Hana dan lambda generik:

#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

pepper_chico
sumber
4
Tolong tolong tolong jangan pergi using namespace boost::fusion(terutama bersama using namespace std). Sekarang tidak ada cara untuk mengetahui apakah itu for_eachadalah std::for_eachatauboost::fusion::for_each
Bulletmagnet
3
@Bulletmagnet ini dilakukan untuk kesederhanaan di sini dan ADL dapat mengatasinya tanpa masalah. Selain itu juga berfungsi lokal.
pepper_chico
16

C ++ memperkenalkan pernyataan ekspansi untuk tujuan ini. Mereka awalnya berada di jalur untuk C ++ 20 tetapi nyaris gagal karena kurangnya waktu untuk tinjauan kata-kata bahasa (lihat di sini dan sini ).

Sintaks yang saat ini disetujui (lihat tautan di atas) adalah:

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

Cara yang lebih sederhana, intuitif, dan ramah kompiler untuk melakukan ini di C ++ 17, menggunakan 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);
}

Ini adalah rekursi waktu kompilasi, mirip dengan yang disajikan oleh @emsr. Tetapi ini tidak menggunakan SFINAE jadi (menurut saya) ini lebih ramah kompiler.

Stypox
sumber
8

Anda perlu menggunakan metaprogramming template, di sini ditunjukkan dengan 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;
}

Di C ++ 0x, Anda dapat menulis print_tuple()sebagai fungsi template variadic.

Marc Mutz - mmutz
sumber
8

Pertama-tama tentukan beberapa pembantu indeks:

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

Dengan fungsi Anda, Anda ingin menerapkan pada setiap elemen tupel:

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

kamu bisa menulis:

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

Atau jika fookembali void, gunakan

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

Catatan: Pada C ++ 14 make_index_sequencesudah ditentukan ( http://en.cppreference.com/w/cpp/utility/integer_sequence ).

Jika Anda memang membutuhkan urutan evaluasi kiri-ke-kanan, pertimbangkan sesuatu seperti ini:

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)>());
}
Philipp H.
sumber
1
Harus mentransmisikan nilai kembali fooke voidsebelum memanggil operator,untuk menghindari kemungkinan kelebihan beban operator patologis.
Yakk - Adam Nevraumont
7

Berikut cara C ++ 17 yang mudah untuk melakukan iterasi pada item tuple hanya dengan pustaka standar:

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

Contoh:

#include <iostream>

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

Keluaran:

1
a

Ini dapat diperpanjang untuk memutus loop secara kondisional jika callable mengembalikan nilai (tetapi masih berfungsi dengan callable yang tidak mengembalikan nilai bool yang dapat dialihkan, misalnya 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)...);
    }
}

Contoh:

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

Keluaran:

1
a
---
1
Marcin Zawiejski
sumber
5

Jika Anda ingin menggunakan std :: tuple dan memiliki compiler C ++ yang mendukung template variadic, coba kode di bawah (diuji dengan g ++ 4.5). Ini harus menjadi jawaban atas pertanyaan Anda.

#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 adalah opsi lain, tetapi membutuhkan jenis tuple sendiri: boost :: fusion :: tuple. Mari lebih baik berpegang pada standar! Ini tesnya:

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

kekuatan template variadic!

sigidagi
sumber
Saya mencoba solusi pertama Anda, tetapi gagal dengan fungsi ini berpasangan. Tahu kenapa? Template <typename T, typename U> void addt (pair <T, U> p) {cout << p.first + p.second << endl; } int main (int argc, char * argv []) {cout << "Halo." << 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); kembali 0; }
pengguna2023370
Sayang sekali jawaban ini ditulis sangat bertele-tele karena menurut saya cara pengulangan (for_each_impl) adalah yang paling elegan dari semua solusi yang pernah saya lihat.
joki
3

Di MSVC STL ada fungsi _For_each_tuple_element (tidak didokumentasikan):

#include <tuple>

// ...

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

Yang lain telah menyebutkan beberapa perpustakaan pihak ketiga yang dirancang dengan baik yang dapat Anda gunakan. Namun, jika Anda menggunakan C ++ tanpa pustaka pihak ketiga tersebut, kode berikut dapat membantu.

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

Catatan: Kode dikompilasi dengan kompiler apa pun yang mendukung C ++ 11, dan menjaga konsistensi dengan desain pustaka standar:

  1. Tupel tidak perlu std::tuple, dan sebagai gantinya dapat berupa apapun yang mendukung std::getdan std::tuple_size; secara khusus, std::arraydan std::pairdapat digunakan;

  2. Tuple mungkin merupakan tipe referensi atau kualifikasi cv;

  3. Ini memiliki perilaku yang mirip dengan std::for_each, dan mengembalikan input UnaryFunction;

  4. Untuk pengguna C ++ 14 (atau versi laster), typename std::enable_if<T>::typedan typename std::decay<T>::typedapat diganti dengan versi yang disederhanakan, std::enable_if_t<T>dan std::decay_t<T>;

  5. Untuk pengguna C ++ 17 (atau versi laster), std::tuple_size<T>::valuedapat diganti dengan versi sederhananya std::tuple_size_v<T>,.

  6. Untuk pengguna C ++ 20 (atau versi laster), SFINAEfitur ini dapat diimplementasikan dengan Concepts.

Graphene
sumber
2

Menggunakan constexprdan if constexpr(C ++ 17) ini cukup sederhana dan mudah:

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);
  }
}
Andreas DM
sumber
1

Saya mungkin ketinggalan kereta ini, tetapi ini akan ada di sini untuk referensi di masa mendatang.
Inilah konstruksi saya berdasarkan jawaban ini dan intinya :

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

Anda kemudian menggunakannya sebagai berikut:

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

    /* ... */
}

Mungkin masih ada ruang untuk perbaikan.


Sesuai kode OP, itu akan menjadi ini:

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

Dari semua jawaban yang pernah saya lihat di sini, di sini dan di sini , saya menyukai cara terbaik @sigidagi untuk mengulang. Sayangnya, jawabannya sangat bertele-tele yang menurut saya mengaburkan kejelasan yang melekat.

Ini adalah versi saya dari solusinya yang lebih ringkas dan berfungsi dengan std::tuple, std::pairdan 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>()
    );
}

Demo: coliru

C ++ 14 std::make_index_sequencedapat diimplementasikan untuk C ++ 11 .

joki
sumber
0

tuple meningkatkan menyediakan fungsi pembantu get_head()dan get_tail()sehingga fungsi pembantu Anda mungkin terlihat seperti ini:

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

seperti dijelaskan di sini http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html

dengan std::tupleitu harus serupa.

Sebenarnya, sayangnya std::tupletampaknya tidak menyediakan antarmuka seperti itu, jadi metode yang disarankan sebelumnya harus berfungsi, atau Anda perlu beralih ke boost::tupleyang memiliki manfaat lain (seperti operator io sudah disediakan). Meskipun ada sisi negatif dari boost::tuplegcc - ia belum menerima template variadic, tapi itu mungkin sudah diperbaiki karena saya tidak memiliki versi boost terbaru yang diinstal pada mesin saya.

Slava
sumber
0

Saya telah tersandung pada masalah yang sama untuk iterasi atas tupel objek fungsi, jadi inilah satu solusi lagi:

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

Keluaran:

A 
B 
C 
D
tmaric
sumber
0

Pilihan lain adalah mengimplementasikan iterator untuk tupel. Ini memiliki keuntungan bahwa Anda dapat menggunakan berbagai algoritme yang disediakan oleh pustaka standar dan loop berbasis rentang. Pendekatan elegan untuk ini dijelaskan di sini https://foonathan.net/2017/03/tuple-iterator/ . Ide dasarnya adalah mengubah tupel menjadi rentang dengan begin()dan end()metode untuk menyediakan iterator. Iterator itu sendiri mengembalikan a std::variant<...>yang kemudian bisa dikunjungi menggunakan std::visit.

Berikut beberapa contohnya:

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

Implementasi saya (yang sangat didasarkan pada penjelasan di tautan di atas):

#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

Akses hanya baca juga didukung dengan meneruskan const std::tuple<>&ke to_range().

Florian Tischler
sumber
0

Memperluas jawaban @Stypox, kita dapat membuat solusi mereka lebih umum (C ++ 17 dan seterusnya). Dengan menambahkan argumen fungsi yang dapat dipanggil:

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

Kemudian, diperlukan strategi untuk mengunjungi setiap jenisnya.

Mari kita mulai dengan beberapa pembantu (dua yang pertama diambil dari 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 digunakan untuk memungkinkan status tupel dimodifikasi.

Pemakaian:

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

Hasil:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

Untuk kelengkapannya, inilah my 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;}
};
Thomas Legris
sumber