Làm cách nào bạn có thể lặp qua các phần tử của std :: tuple?


112

Làm cách nào để tôi có thể lặp qua một tuple (sử dụng C ++ 11)? Tôi đã thử những cách sau:

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

nhưng điều này không hoạt động:

Lỗi 1: xin lỗi, chưa hoàn thành: không thể mở rộng 'Trình nghe ...' thành danh sách đối số có độ dài cố định.
Lỗi 2: Tôi không thể xuất hiện trong một biểu thức hằng số.

Vì vậy, làm cách nào để lặp lại chính xác các phần tử của một bộ tuple?


2
Tôi có thể hỏi, làm thế nào bạn biên dịch trong C ++ 0x? Theo như tôi biết thì nó chưa được phát hành và cũng chưa sẵn sàng.
Burkhard,

5
g ++ có hỗ trợ thử nghiệm một số tính năng C ++ 0X, bao gồm các mẫu khác nhau, kể từ phiên bản 4.3. Trình biên dịch khác làm tương tự (với bộ tính năng khác nhau, nếu bạn muốn sử dụng chúng trong sản xuất, bạn đang trở lại trong 90 với một biến thể rộng hỗ trợ cho chảy máu cạnh thứ)
AProgrammer

Tôi đang sử dụng g ++ phiên bản 4.4 với std = c ++ 0x

9
Câu hỏi này cần cập nhật C ++ 11.
Omnifarious

2
@Omnifarious bây giờ, nó cần một C ++ 14 cập nhật
pepper_chico

Câu trả lời:


26

Boost.Fusion là một khả năng:

Ví dụ chưa được kiểm chứng:

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

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

@ViktorSehr AFAICT nó không (ít nhất là trên GCC 4.7.2)? Bất cứ ai có một gợi ý?
sehe

@ViktorSehr Tìm thấy vấn đề: một lỗi / thiếu sót gây ra hành vi của phản ứng tổng hợp phụ thuộc vào thứ tự của các bao gồm, xem vé Boost # 8418 để biết thêm chi tiết
sehe

cần phải sử dụng boost :: fusion :: tuple thay vì std :: tuple để điều này hoạt động.
Marcin

Theo GCC 8.1 / mingw-64, tôi nhận được hai cảnh báo về việc sử dụng boost :: fusion :: for_each với biểu thức std lambda: boost / mpl / khẳng định.hpp: 188: 21: warning: dấu ngoặc đơn không cần thiết trong khai báo 'khẳng định_arg' [-Wp ngoặc đơn] không thành công ************ (Pred :: ************ boost / mpl / khẳng định.hpp: 193: 21: cảnh báo: dấu ngoặc đơn không cần thiết trong khai báo 'khẳng định_not_arg' [- Dấu ngoặc đơn] không thành công ************ (boost :: mpl :: not_ <Pred> :: ************
Hossein

129

Tôi có câu trả lời dựa trên Lặp lại trên 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);
}

Ý tưởng thông thường là sử dụng đệ quy thời gian biên dịch. Trên thực tế, ý tưởng này được sử dụng để tạo ra một printf loại an toàn như đã lưu ý trong các giấy tuple gốc.

Điều này có thể dễ dàng khái quát thành một for_eachbộ giá trị:

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

Mặc dù điều này sau đó đòi hỏi một số nỗ lực để FuncTđại diện một cái gì đó với các quá tải thích hợp cho mọi loại tuple có thể chứa. Điều này hoạt động tốt nhất nếu bạn biết tất cả các phần tử tuple sẽ chia sẻ một lớp cơ sở chung hoặc một cái gì đó tương tự.


5
Cảm ơn vì ví dụ đơn giản tốt đẹp. Đối với người mới bắt đầu C ++ đang tìm kiếm thông tin cơ bản về cách hoạt động của tính năng này, hãy xem SFINAEenable_iftài liệu .
Faheem Mitha

Điều này có thể dễ dàng được khái quát thành một cái chung chung for_each. Trên thực tế, tôi đã tự mình làm điều đó. :-) Tôi nghĩ câu trả lời này sẽ hữu ích hơn nếu nó đã được khái quát hóa.
Omnifarious

4
Ở đó, tôi đã thêm phần khái quát vì tôi thực sự cần một cái và tôi nghĩ nó sẽ hữu ích cho những người khác xem.
Omnifarious

2
Lưu ý: Bạn cũng có thể cần các phiên bản có const std::tuple<Tp...>&.. Nếu bạn không có ý định sửa đổi các bộ giá trị trong khi lặp lại, các constphiên bản đó sẽ đủ.
lethal-guitar

2
Không như đã viết .. Bạn có thể tạo một phiên bản có lật chỉ mục - bắt đầu từ I = sizeof ... (Tp) và đếm ngược. Sau đó, cung cấp số lượng tối đa của args một cách rõ ràng. Bạn cũng có thể tạo một phiên bản bị hỏng trên một loại thẻ, chẳng hạn như break_t. Sau đó, bạn sẽ đặt một đối tượng của loại thẻ đó vào bộ mã của mình khi bạn muốn ngừng in. Hoặc bạn có thể cung cấp loại dừng dưới dạng parm mẫu. Rõ ràng là bạn không thể phá vỡ trong thời gian chạy.
emsr

55

Trong C ++ 17, bạn có thể sử dụng std::applyvới biểu thức gấp :

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

Một ví dụ hoàn chỉnh để in một 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);
}

[Ví dụ trực tuyến trên Coliru]

Giải pháp này giải quyết vấn đề thứ tự đánh giá trong câu trả lời của M. Alaggan .


1
Bạn có thể giải thích những gì đang xảy ra ở đây ((std::cout << args << '\n'), ...);:? Lambda được gọi một lần với các phần tử tuple được giải nén args, nhưng điều gì xảy ra với dấu ngoặc kép?
helmesjo

4
@helmesjo Nó mở rộng thành biểu thức dấu phẩy ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n'))ở đây.
xskxzr

Lưu ý rằng trong trường hợp bạn muốn thực hiện những việc không hợp pháp trong biểu thức dấu phẩy (chẳng hạn như khai báo các biến và khối), bạn có thể đặt tất cả những điều đó vào một phương thức và chỉ cần gọi nó từ bên trong biểu thức dấu phẩy được gấp lại.
Miral

24

Trong C ++ 17, bạn có thể làm điều này:

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

Điều này đã hoạt động trong Clang ++ 3.9, sử dụng std :: Experiment :: apply.


4
Điều này không dẫn đến việc lặp lại - tức là các lệnh gọi do_something()- xảy ra theo một thứ tự không xác định, bởi vì gói tham số được mở rộng trong một lệnh gọi hàm (), trong đó các đối số có thứ tự không xác định? Điều đó có thể rất quan trọng; Tôi tưởng tượng rằng hầu hết mọi người sẽ mong đợi thứ tự được đảm bảo xảy ra theo thứ tự như các thành viên, tức là như các chỉ số std::get<>(). AFAIK, để có được thứ tự đảm bảo trong những trường hợp như thế này, việc mở rộng phải được thực hiện bên trong {braces}. Liệu tôi có sai? Điều này đặt câu trả lời nhấn mạnh vào trật tự như vậy: stackoverflow.com/a/16387374/2757035
underscore_d

21

Sử dụng Boost.Hana và lambdas chung:

#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
Xin vui lòng vui lòng không đi using namespace boost::fusion(đặc biệt là cùng với using namespace std). Bây giờ không có cách nào để biết liệu rằng for_eachstd::for_eachhayboost::fusion::for_each
Bulletmagnet

3
@Bulletmagnet điều này đã được thực hiện cho ngắn gọn ở đây và ADL có thể xử lý điều đó mà không có vấn đề gì. Bên cạnh đó, nó cũng hoạt động cục bộ.
Pepper_chico

16

C ++ đang giới thiệu các câu lệnh mở rộng cho mục đích này. Ban đầu, họ đang theo dõi C ++ 20 nhưng suýt chút nữa đã bỏ lỡ việc cắt giảm do thiếu thời gian để xem xét từ ngữ ngôn ngữ (xem tại đâytại đây ).

Cú pháp hiện được đồng ý (xem các liên kết ở trên) là:

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

15

Một cách đơn giản hơn, trực quan và thân thiện với trình biên dịch để thực hiện việc này trong C ++ 17, sử dụng 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);
}

Đây là đệ quy thời gian biên dịch, tương tự như đệ quy được trình bày bởi @emsr. Nhưng điều này không sử dụng SFINAE nên (tôi nghĩ) nó thân thiện với trình biên dịch hơn.


8

Bạn cần sử dụng lập trình ẩn mẫu, được hiển thị ở đây với 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;
}

Trong C ++ 0x, bạn có thể viết print_tuple()dưới dạng một hàm mẫu đa dạng.


8

Đầu tiên xác định một số trình trợ giúp chỉ mục:

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

Với chức năng của mình, bạn muốn áp dụng trên từng phần tử tuple:

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

bạn có thể viế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)>());
}

Hoặc nếu footrả lại void, sử dụng

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

Lưu ý: Trên C ++ 14 make_index_sequenceđã được định nghĩa ( http://en.cppreference.com/w/cpp/utility/integer_sequence ).

Nếu bạn thực sự cần một thứ tự đánh giá từ trái sang phải, hãy xem xét điều gì đó như sau:

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
Nên đúc giá trị trả về foođể voidtrước khi gọi operator,để tránh khả năng điều hành quá tải bệnh lý.
Yakk - Adam Nevraumont

7

Đây là một cách dễ dàng trong C ++ 17 để lặp qua nhiều mục chỉ với thư viện tiêu chuẩn:

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

Thí dụ:

#include <iostream>

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

Đầu ra:

1
a

Điều này có thể được mở rộng để phá vỡ vòng lặp có điều kiện trong trường hợp có thể gọi trả về một giá trị (nhưng vẫn hoạt động với các có thể gọi không trả về giá trị có thể gán bool, ví dụ: 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)...);
    }
}

Thí dụ:

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

Đầu ra:

1
a
---
1

5

Nếu bạn muốn sử dụng std :: tuple và bạn có trình biên dịch C ++ hỗ trợ các mẫu đa dạng, hãy thử mã dưới đây (được thử nghiệm với g ++ 4.5). Đây sẽ là câu trả lời cho câu hỏi của bạn.

#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 là một tùy chọn khác, nhưng nó yêu cầu loại tuple riêng: boost :: fusion :: tuple. Hãy bám sát tiêu chuẩn tốt hơn! Đây là một bài kiểm tra:

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

sức mạnh của các mẫu khác nhau!


Tôi đã thử giải pháp đầu tiên của bạn, nhưng nó không thành công với chức năng này trên các cặp. Có ý kiến ​​tại sao không? Template <typename T, typename U> void addt (pair <T, U> p) {cout << p.first + p.second << endl; } int main (int argc, char * argv []) {cout << "Xin chào." << 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); trả về 0; }
user2023370

Thật tiếc câu trả lời này được viết quá dài dòng bởi vì tôi nghĩ rằng cách lặp (for_each_impl) là cách thanh lịch nhất trong tất cả các giải pháp mà tôi đã thấy.
joki

3

Trong MSVC STL có một hàm _For_each_tuple_element (không được ghi lại):

#include <tuple>

// ...

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

2

Những người khác đã đề cập đến một số thư viện của bên thứ ba được thiết kế tốt mà bạn có thể chuyển sang. Tuy nhiên, nếu bạn đang sử dụng C ++ mà không có các thư viện của bên thứ ba đó, đoạn mã sau có thể hữu ích.

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

Lưu ý: Mã biên dịch với bất kỳ trình biên dịch nào hỗ trợ C ++ 11 và nó giữ tính nhất quán với thiết kế của thư viện chuẩn:

  1. Tuple không cần thiết std::tuple, thay vào đó có thể là bất kỳ thứ gì hỗ trợ std::getstd::tuple_size; đặc biệt, std::arraystd::paircó thể được sử dụng;

  2. Tuple có thể là loại tham chiếu hoặc cv đủ tiêu chuẩn;

  3. Nó có hành vi tương tự như std::for_eachvà trả về đầu vào UnaryFunction;

  4. Đối với người dùng C ++ 14 (hoặc phiên bản cao hơn) typename std::enable_if<T>::typetypename std::decay<T>::typecó thể được thay thế bằng phiên bản đơn giản của họ, std::enable_if_t<T>std::decay_t<T>;

  5. Đối với người dùng C ++ 17 (hoặc phiên bản cao hơn), std::tuple_size<T>::valuecó thể được thay thế bằng phiên bản đơn giản của nó std::tuple_size_v<T>,.

  6. Đối với người dùng C ++ 20 (hoặc phiên bản cao hơn), SFINAEtính năng này có thể được triển khai với Concepts.


2

Sử dụng constexprif constexpr(C ++ 17) điều này khá đơn giản và dễ hiểu:

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

Tôi có thể đã bỏ lỡ chuyến tàu này, nhưng điều này sẽ ở đây để tham khảo trong tương lai.
Đây là cấu trúc của tôi dựa trên câu trả lời này và trên ý chính này :

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

Sau đó, bạn sử dụng nó như sau:

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

    /* ... */
}

Có thể có chỗ cho những cải tiến.


Theo mã của OP, nó sẽ trở thành như sau:

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

Trong số tất cả các câu trả lời tôi đã thấy ở đây, ở đâyở đây , tôi thích cách lặp lại của @sigidagi nhất. Thật không may, câu trả lời của anh ấy rất dài dòng mà theo tôi đã che khuất sự rõ ràng vốn có.

Đây là phiên bản của tôi về giải pháp của anh ấy ngắn gọn hơn và phù hợp với std::tuple, std::pairstd::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_sequencecó thể được triển khai cho C ++ 11 .


0

boost's tuple cung cấp các chức năng trợ giúp get_head()get_tail()vì vậy các chức năng trợ giúp của bạn có thể trông giống như sau:

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

như được mô tả tại đây http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html

với std::tuplenó phải tương tự.

Trên thực tế, rất tiếc, std::tupledường như không cung cấp giao diện như vậy, vì vậy các phương pháp được đề xuất trước sẽ hoạt động, hoặc bạn sẽ cần chuyển sang phương pháp boost::tuplecó các lợi ích khác (như các nhà khai thác io đã được cung cấp). Mặc dù có nhược điểm của boost::tuplegcc - nó chưa chấp nhận các mẫu khác nhau, nhưng điều đó có thể đã được khắc phục vì tôi chưa cài đặt phiên bản boost mới nhất trên máy của mình.


0

Tôi đã gặp phải vấn đề tương tự khi lặp qua nhiều đối tượng hàm, vì vậy đây là một giải pháp khác:

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

Đầu ra:

A 
B 
C 
D

0

Một tùy chọn khác sẽ là triển khai các trình vòng lặp cho các bộ giá trị. Điều này có lợi thế là bạn có thể sử dụng nhiều thuật toán khác nhau được cung cấp bởi thư viện tiêu chuẩn và dựa trên phạm vi cho các vòng lặp. Một cách tiếp cận thanh lịch cho điều này được giải thích tại đây https://foonathan.net/2017/03/tuple-iterator/ . Ý tưởng cơ bản là biến các bộ giá trị thành một phạm vi begin()end()các phương thức để cung cấp trình vòng lặp. Bản thân trình vòng lặp trả về a std::variant<...>mà sau đó có thể được truy cập bằng cách sử dụngstd::visit .

Dưới đây là một số ví dụ:

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

Cách triển khai của tôi (dựa nhiều vào các giải thích trong liên kết ở trên):

#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

Quyền truy cập chỉ đọc cũng được hỗ trợ bằng cách chuyển const std::tuple<>&đến to_range().


0

Mở rộng trên câu trả lời @Stypox, chúng tôi có thể làm cho giải pháp của họ chung chung hơn (C ++ 17 trở đi). Bằng cách thêm đối số hàm có thể gọi:

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

Sau đó, chúng ta cần một chiến lược để truy cập từng loại.

Hãy bắt đầu với một số người trợ giúp (hai người đầu tiên lấy từ 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 được sử dụng để cho phép sửa đổi trạng thái của bộ giá trị.

Sử dụng:

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

Kết quả:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

Để hoàn thiện, đây là 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;}
};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.