Da ich mit dem, was ich gefunden habe, nicht zufrieden war, habe ich versucht, selbst eine Lösung zu finden, und schließlich eine kleine Bibliothek geschrieben , mit der generische Operationen für Argumentpakete formuliert werden können. Meine Lösung bietet folgende Funktionen:
- Ermöglicht das Durchlaufen aller oder einiger Elemente eines Argumentpakets, möglicherweise angegeben durch Berechnen ihrer Indizes im Paket.
- Ermöglicht die Weiterleitung berechneter Teile eines Argumentpakets an verschiedene Funktoren.
- Erfordert nur das Einfügen einer relativ kurzen Header-Datei.
- Verwendet die perfekte Weiterleitung in großem Umfang, um starkes Inlining zu ermöglichen, und vermeidet unnötige Kopien / Verschiebungen, um einen minimalen Leistungsverlust zu ermöglichen.
- Die interne Implementierung der iterierenden Algorithmen basiert auf der Optimierung der leeren Basisklasse, um den Speicherverbrauch zu minimieren.
- Es ist einfach (relativ, wenn man bedenkt, dass es sich um eine Meta-Programmierung für Vorlagen handelt), zu erweitern und anzupassen.
Ich werde zuerst zeigen, was getan werden kann mit der Bibliothek gemacht werden kann, und dann ihre Implementierung veröffentlichen .
ANWENDUNGSFÄLLE
Hier ist ein Beispiel, wie die for_each_in_arg_pack()
Funktion verwendet werden kann, um alle Argumente eines Pakets zu durchlaufen und jedes Argument in der Eingabe an einen vom Client bereitgestellten Funktor zu übergeben (natürlich muss der Funktor einen generischen Aufrufoperator haben, wenn das Argumentpaket Werte enthält heterogener Typen):
struct print
{
template<typename T>
void operator () (T&& t)
{
cout << t << endl;
}
};
template<typename... Ts>
void print_all(Ts&&... args)
{
for_each_in_arg_pack(print(), forward<Ts>(args)...);
}
Der print
obige Funktor kann auch für komplexere Berechnungen verwendet werden. Hier ist insbesondere, wie man eine Teilmenge iterieren würde (in diesem Fall a Teilbereich ) der Argumente in einem Paket durchlaufen:
template<typename... Ts>
void split_and_print(Ts&&... args)
{
constexpr size_t packSize = sizeof...(args);
constexpr size_t halfSize = packSize / 2;
cout << "Printing first half:" << endl;
for_each_in_arg_pack_subset(
print(),
index_range<0, halfSize>(),
forward<Ts>(args)...
);
cout << "Printing second half:" << endl;
for_each_in_arg_pack_subset(
print(),
index_range<halfSize, packSize>(),
forward<Ts>(args)...
);
}
Manchmal möchte man vielleicht nur einen Teil eines Argumentationspakets an einen anderen variadischen Funktor weiterleiten, anstatt seine Elemente zu durchlaufen und jedes einzeln an einen nicht variadischen Funktor zu übergeben. Das ist was dieforward_subpack()
ermöglicht Algorithmus:
struct my_func
{
template<typename... Ts>
void operator ()(Ts&&... args)
{
print_all(forward<Ts>(args)...);
}
};
template<typename... Ts>
void split_and_print(Ts&&... args)
{
constexpr size_t packSize = sizeof...(args);
constexpr size_t halfSize = packSize / 2;
cout << "Printing first half:" << endl;
forward_subpack(my_func(), index_range<0, halfSize>(), forward<Ts>(args)...);
cout << "Printing second half:" << endl;
forward_subpack(my_func(), index_range<halfSize, packSize>(), forward<Ts>(args)...);
}
Für spezifischere Aufgaben ist es natürlich möglich, bestimmte Argumente in einem Paket durch Indizieren abzurufen . Dies nth_value_of()
ermöglicht die Funktion zusammen mit ihren Helfern first_value_of()
und last_value_of()
:
template<unsigned I, typename... Ts>
void print_first_last_and_indexed(Ts&&... args)
{
cout << "First argument: " << first_value_of(forward<Ts>(args)...) << endl;
cout << "Last argument: " << last_value_of(forward<Ts>(args)...) << endl;
cout << "Argument #" << I << ": " << nth_value_of<I>(forward<Ts>(args)...) << endl;
}
Wenn das Argumentpaket andererseits homogen ist (dh alle Argumente haben den gleichen Typ), ist möglicherweise eine Formulierung wie die folgende vorzuziehen. Dasis_homogeneous_pack<>
Metafunktion ermöglicht die Bestimmung, ob alle Typen in einem Parameterpaket homogen sind, und soll hauptsächlich in static_assert()
Anweisungen verwendet werden:
template<typename... Ts>
void print_all(Ts&&... args)
{
static_assert(
is_homogeneous_pack<Ts...>::value,
"Template parameter pack not homogeneous!"
);
for (auto&& x : { args... })
{
}
cout << endl;
}
Da Lambdas nur syntaktischer Zucker für Funktoren sind, können sie auch in Kombination mit den oben genannten Algorithmen verwendet werden. Bis generische Lambdas von C ++ unterstützt werden, ist dies jedoch nur für homogene Argumentpakete möglich . Das folgende Beispiel zeigt auch die Verwendung vonhomogeneous-type<>
Metafunktion, die den Typ aller Argumente in einem homogenen Paket zurückgibt:
static_assert(
is_homogeneous_pack<Ts...>::value,
"Template parameter pack not homogeneous!"
);
using type = homogeneous_type<Ts...>::type;
for_each_in_arg_pack([] (type const& x) { cout << x << endl; }, forward<Ts>(args)...);
Dies ist im Grunde das, was die Bibliothek erlaubt, aber ich glaube es könnte sogar erweitert werden , um komplexere Aufgaben auszuführen.
IMPLEMENTIERUNG
Jetzt kommt die Implementierung, die an sich etwas schwierig ist, daher werde ich mich auf Kommentare verlassen, um den Code zu erklären und zu vermeiden, dass dieser Beitrag zu lang wird (vielleicht ist er es bereits):
#include <type_traits>
#include <utility>
template<int I, typename... Ts>
struct nth_type_of
{
};
template<typename T, typename... Ts>
struct nth_type_of<0, T, Ts...>
{
using type = T;
};
template<int I, typename T, typename... Ts>
struct nth_type_of<I, T, Ts...>
{
using type = typename nth_type_of<I - 1, Ts...>::type;
};
template<typename... Ts>
struct first_type_of
{
using type = typename nth_type_of<0, Ts...>::type;
};
template<typename... Ts>
struct last_type_of
{
using type = typename nth_type_of<sizeof...(Ts) - 1, Ts...>::type;
};
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
typename std::enable_if<(I == 0), decltype(std::forward<T>(t))>::type
{
return std::forward<T>(t);
}
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
typename std::enable_if<(I > 0), decltype(
std::forward<typename nth_type_of<I, T, Ts...>::type>(
std::declval<typename nth_type_of<I, T, Ts...>::type>()
)
)>::type
{
using return_type = typename nth_type_of<I, T, Ts...>::type;
return std::forward<return_type>(nth_value_of<I - 1>((std::forward<Ts>(args))...));
}
template<typename... Ts>
auto first_value_of(Ts&&... args) ->
decltype(
std::forward<typename first_type_of<Ts...>::type>(
std::declval<typename first_type_of<Ts...>::type>()
)
)
{
using return_type = typename first_type_of<Ts...>::type;
return std::forward<return_type>(nth_value_of<0>((std::forward<Ts>(args))...));
}
template<typename... Ts>
auto last_value_of(Ts&&... args) ->
decltype(
std::forward<typename last_type_of<Ts...>::type>(
std::declval<typename last_type_of<Ts...>::type>()
)
)
{
using return_type = typename last_type_of<Ts...>::type;
return std::forward<return_type>(nth_value_of<sizeof...(Ts) - 1>((std::forward<Ts>(args))...));
}
struct null_type
{
};
template<typename... Ts>
struct homogeneous_type;
template<typename T>
struct homogeneous_type<T>
{
using type = T;
static const bool isHomogeneous = true;
};
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
using type_of_remaining_parameters = typename homogeneous_type<Ts...>::type;
static const bool isHomogeneous = std::is_same<T, type_of_remaining_parameters>::value;
using type = typename std::conditional<isHomogeneous, T, null_type>::type;
};
template<typename... Ts>
struct is_homogeneous_pack
{
static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};
template <unsigned... Is>
struct index_list
{
};
namespace detail
{
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder;
template <unsigned MIN, unsigned... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{
};
}
template<unsigned MIN, unsigned MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
namespace detail
{
template<unsigned I, typename T>
struct invoker_base
{
template<typename F, typename U>
invoker_base(F&& f, U&& u) { f(u); }
};
template<unsigned I, typename T>
struct indexed_type
{
static const unsigned int index = I;
using type = T;
};
template<typename... Ts>
struct invoker : public invoker_base<Ts::index, typename Ts::type>...
{
template<typename F, typename... Us>
invoker(F&& f, Us&&... args)
:
invoker_base<Ts::index, typename Ts::type>(std::forward<F>(f), std::forward<Us>(args))...
{
}
};
}
template<typename F, unsigned... Is, typename... Ts>
void for_each_in_arg_pack_subset(F&& f, index_list<Is...> const& i, Ts&&... args)
{
detail::invoker<detail::indexed_type<Is, typename nth_type_of<Is, Ts...>::type>...> invoker(
f,
(nth_value_of<Is>(std::forward<Ts>(args)...))...
);
}
template<typename F, typename... Ts>
void for_each_in_arg_pack(F&& f, Ts&&... args)
{
for_each_in_arg_pack_subset(f, index_range<0, sizeof...(Ts)>(), std::forward<Ts>(args)...);
}
template<typename F, unsigned... Is, typename... Ts>
void forward_subpack(F&& f, index_list<Is...> const& i, Ts&&... args)
{
f((nth_value_of<Is>(std::forward<Ts>(args)...))...);
}
template<typename F, typename... Ts>
void forward_pack(F&& f, Ts&&... args)
{
f(std::forward<Ts>(args)...);
}
FAZIT
Obwohl ich meine eigene Antwort auf diese Frage gegeben habe (und tatsächlich aufgrund dieser Tatsache), bin ich natürlich gespannt, ob es alternative oder bessere Lösungen gibt, die ich verpasst habe - abgesehen von den im Abschnitt "Verwandte Werke" genannten der Frage.
foo(args);...