Wie kann ich über ein Tuple iterieren (mit C++ 11)? Ich habe folgendes versucht:
for(int i=0; i<std::Tuple_size<T...>::value; ++i)
std::get<i>(my_Tuple).do_sth();
aber das funktioniert nicht:
Fehler 1: Entschuldigung, nicht implementiert: "Listener ..." kann nicht in eine Liste mit fester Länge erweitert werden.
Fehler 2: Ich kann nicht in einem konstanten Ausdruck erscheinen.
Wie kann ich also die Elemente eines Tupels richtig durchlaufen?
Boost.Fusion ist eine Möglichkeit:
Ungetestetes Beispiel:
struct DoSomething
{
template<typename T>
void operator()(T& t) const
{
t.do_sth();
}
};
Tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());
Ich habe eine Antwort basierend auf über einen Tupel iterieren :
#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);
}
Die übliche Idee ist die Rekursion der Kompilierzeit. In der Tat wird diese Idee verwendet, um eine printf zu machen, die typsicher ist, wie in den Original-Tuple-Papieren vermerkt.
Dies kann leicht zu einem for_each
für Tupel verallgemeinert werden:
#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);
}
Dies erfordert jedoch einige Anstrengungen, um FuncT
etwas mit den entsprechenden Überladungen für jeden Typ, den das Tuple enthalten kann, darstellen zu lassen. Dies funktioniert am besten, wenn Sie wissen, dass alle Tuple-Elemente eine gemeinsame Basisklasse oder etwas Ähnliches haben.
Verwenden Sie Boost.Hana und generische Lambdas:
#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;
}
In C++ 17 können Sie Folgendes tun:
std::apply([](auto ...x){std::make_Tuple(x.do_something()...);} , the_Tuple);
Dies funktioniert bereits in Clang ++ 3.9 mit std :: experimental :: apply.
Sie müssen die Template-Metaprogrammierung verwenden, die hier mit Boost.Tuple dargestellt ist:
#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;
}
In C++ 0x können Sie stattdessen print_Tuple()
als variadische Vorlagenfunktion schreiben.
C++ führt zu diesem Zweck Erweiterungsanweisungen ein. Sie befanden sich ursprünglich auf dem Weg zu C++ 20, verfehlten jedoch den Schnitt aufgrund fehlender Zeit für die Überprüfung der Sprachformulierungen (siehe hier und hier ).
Die derzeit vereinbarte Syntax (siehe die obigen Links) lautet:
{
auto tup = std::make_Tuple(0, 'a', 3.14);
template for (auto elem : tup)
std::cout << elem << std::endl;
}
In C++ 17 können Sie std::apply
mit fold Ausdruck verwenden:
std::apply([](auto&&... args) {((/* args.dosomething() */), ...);}, the_Tuple);
Ein vollständiges Beispiel zum Drucken eines Tupels:
#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);
}
Diese Lösung löst die Frage der Evaluierungsreihenfolge in M. Alaggans Antwort .
Definieren Sie zunächst einige Indexhelfer:
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...> {};
Mit Ihrer Funktion möchten Sie sich für jedes Tuple-Element bewerben:
template <typename T>
/* ... */ foo(T t) { /* ... */ }
du kannst schreiben:
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)>());
}
Wenn foo
void
zurückgibt, verwenden Sie
std::tie((foo(std::get<I>(ts)), 1) ... );
Hinweis: In C++ 14 ist make_index_sequence
bereits definiert ( http://en.cppreference.com/w/cpp/utility/integer_sequence ).
Wenn Sie eine Evaluierungsreihenfolge von links nach rechts benötigen, sollten Sie Folgendes berücksichtigen:
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)>());
}
Wenn Sie std :: Tuple verwenden möchten und einen C++ - Compiler verwenden, der variadic-Templates unterstützt, versuchen Sie den folgenden Code (getestet mit g ++ 4.5). Dies sollte die Antwort auf Ihre Frage sein.
#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 ist eine weitere Option, erfordert jedoch einen eigenen Tuple-Typ: boost :: fusion :: Tuple. Bleibt besser beim Standard! Hier ist ein Test:
#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;
}
die kraft variabischer vorlagen!
Hier ist eine einfache C++ 17-Methode zum Durchlaufen von Tuple-Elementen mit einer Standardbibliothek:
#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)...);
}
}
Beispiel:
#include <iostream>
int main()
{
std::Tuple<int, char> items{1, 'a'};
for_each(items, [](const auto& item) {
std::cout << item << "\n";
});
}
Ausgabe:
1
a
Dies kann erweitert werden, um die Schleife bedingt zu unterbrechen, falls das Callable einen Wert zurückgibt (aber mit Callables funktioniert, die keinen bool zuweisbaren Wert zurückgeben, z.
#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)...);
}
}
Beispiel:
#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;
});
}
Ausgabe:
1
a
---
1
das Tuple des Boobs bietet Hilfsfunktionen get_head()
und get_tail()
, so dass Ihre Hilfsfunktionen so aussehen können:
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()); }
wie hier beschrieben http://www.boost.org/doc/libs/1_34_0/libs/Tuple/doc/Tuple_advanced_interface.html
mit std::Tuple
sollte es ähnlich sein.
Leider scheint std::Tuple
keine solche Schnittstelle zu bieten, so dass die zuvor vorgeschlagenen Methoden funktionieren sollten, oder Sie müssten zu boost::Tuple
wechseln, was andere Vorteile hat (wie z. B. bereits vorhandene io-Operatoren). Es gibt zwar einen Nachteil von boost::Tuple
mit gcc - es akzeptiert noch keine variadischen Vorlagen, aber das kann schon behoben sein, da auf meinem Rechner keine neueste Boost-Version installiert ist.
Eine einfachere, intuitivere und compilerfreundlichere Möglichkeit, dies in C++ 17 mit if constexpr
zu tun:
// 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);
}
Dies ist eine Rekursion während der Kompilierung, ähnlich der von @emsr. Aber dies verwendet SFINAE nicht, also (glaube ich), ist es compilerfreundlicher.
Ich könnte diesen Zug vermisst haben, aber dies wird hier als Referenz dienen.
Hier ist mein Konstrukt basierend auf dieser Antwort und diesem 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){}
};
Sie verwenden es dann wie folgt:
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);
/* ... */
}
Es könnte Raum für Verbesserungen geben.
Gemäß dem Code des OPs würde dies folgendermaßen aussehen:
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);
In MSVC STL gibt es eine _For_each_Tuple_element-Funktion (nicht dokumentiert):
#include <Tuple>
// ...
std::Tuple<int, char, float> values{};
std::_For_each_Tuple_element(values, [](auto&& value)
{
// process 'value'
});
Die Verwendung von constexpr
NAME _ und if constexpr
(C++ 17) ist recht einfach und unkompliziert:
_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);
}
}
_
Von allen Antworten, die ich hier gesehen habe, - hier und hier , hat mir gefallen - @sigidagi 's am besten durchlaufen. Leider ist seine Antwort sehr ausführlich, was meiner Meinung nach die inhärente Klarheit verdeckt.
Dies ist meine Version seiner Lösung, die übersichtlicher ist und mit std::Tuple
, std::pair
und std::array
arbeitet.
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
std::make_index_sequence
von C++ 14 kann für C++ 11 implementiert werden.
Andere haben einige gut konzipierte Bibliotheken von Drittanbietern erwähnt, an die Sie sich wenden können. Wenn Sie jedoch C++ ohne diese Fremdanbieter-Bibliotheken verwenden, kann der folgende Code hilfreich sein.
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);
}
Anmerkung: Der Code wird mit jedem Compiler kompiliert, der C++ 11 unterstützt, und er bleibt mit dem Design der Standardbibliothek konsistent:
Das Tuple muss nicht std::Tuple
sein, sondern kann alles sein, das std::get
und std::Tuple_size
unterstützt. insbesondere können std::array
und std::pair
verwendet werden;
Das Tupel kann ein Referenztyp sein oder cv-qualifiziert sein;
Es verhält sich ähnlich wie std::for_each
und gibt die Eingabe UnaryFunction
zurück.
Für C++ 14 (oder spätere Version) Benutzer könnten typename std::enable_if<T>::type
und typename std::decay<T>::type
durch ihre vereinfachte Version std::enable_if_t<T>
und std::decay_t<T>
ersetzt werden.
Für C++ 17-Benutzer (oder die spätere Version) kann std::Tuple_size<T>::value
durch die vereinfachte Version std::Tuple_size_v<T>
ersetzt werden.
Für C++ 20-Benutzer (oder die spätere Version) kann die SFINAE
-Funktion mit der Concepts
implementiert werden.
Ich bin über dasselbe Problem gestolpert, weil ich über ein Tuple von Funktionsobjekten iteriert habe. Hier ist eine weitere Lösung:
#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;
}
Ausgabe:
A
B
C
D