Argumentzähler bei auto Parametern
-
Hi,
ich habe eine Klasse, der man eine Funktion mitgeben kann. Je nachdem, ob die übergebene Funktion einen oder zwei Parameter entgegen nimmt, hat die Klasse eine per enable_if aktivierte entsprechende Methode, die die Funktion verwendet.
Minimalbeispiel:
#include <iostream> template <typename T> struct get_arity : get_arity<decltype(&T::operator())> {}; template <typename R, typename... Args> struct get_arity<R(*)(Args...)> : std::integral_constant<unsigned, sizeof...(Args)> {}; template <typename R, typename C, typename... Args> struct get_arity<R(C::*)(Args...)> : std::integral_constant<unsigned, sizeof...(Args)> {}; template <typename R, typename C, typename... Args> struct get_arity<R(C::*)(Args...) const> : std::integral_constant<unsigned, sizeof...(Args)> {}; template<typename T> void print(T fn) { std::cout << get_arity<T>::value << "\n"; } int main() { print([](){}); print([](int){}); print([](int, int){}); return 0; }
Das funktioniert so und gibt 0,1,2 aus. Um den Typ der Parameter nicht angegeben zu müssen, wollte ich auto verwenden. Ein Lambda mit einem auto Parameter (z.B.:)
print([](auto){});
erzeugt im GCC die Meldung
prog.cpp: In instantiation of 'struct get_arity<main()::<lambda(auto:1)> >': prog.cpp:16:12: required from 'void print(T) [with T = main()::<lambda(auto:1)>]' prog.cpp:21:18: required from here prog.cpp:5:8: error: decltype cannot resolve address of overloaded function struct get_arity : get_arity<decltype(&T::operator())> {}; ^ prog.cpp: In instantiation of 'void print(T) [with T = main()::<lambda(auto:1)>]': prog.cpp:21:18: required from here prog.cpp:16:12: error: 'value' is not a member of 'get_arity<main()::<lambda(auto:1)> >' std::cout << get_arity<T>::value << "\n"; ^
und in VS2015 die etwas aussagekräftigere Meldung
C3539 Ein Vorlagenargument darf keinen Typ aufweisen, der "auto" enthält
Kriegt man das auch irgendwie mit den auto Lambdas hin?
-
Hiho,
also ohne dir jetzt eine genaue ISO-Regel nennen zu können, die das erklärt: deine print-Funktion ist ein Template. Der Compiler soll hier also aus dem Argument den Typ ableiten. Das Argument selbst ist aber ein Lambda mit auto, wo auch eine Typherleitung notwendig ist über den Parametertyp (der ja wie grad geschrieben hergeleitet werden muss). Da beißt sicht die Katze in den Schwanz.
VG
Pellaeon
-
Aber "auto" ist ja nur 1 Parameter. An der Stelle ist mir ja total egal, was auto für einen Typ hat, ich will nur die Anzahl der autos haben. Und falls du das missverstanden hast, auto lambdas sind bei der print Funktion kein Problem, erst wenn ich die Anzahl der Parameter wissen möchte wird auto zum Problem.
template<typename T> void print(T fn) { fn(1, 2); } print([](auto&& x, auto&& y) { std::cout << x << y; });
Das funktioniert ganz normal. Hier nochmal ein Minimalbeispiel von dem was ich erreichen möchte:
template<typename T> struct Printer { T fn; explicit Printer(T _fn) : fn(std::move(_fn)) { } //das print, falls fn 1 Parameter nimmt void print() { fn(1); } //das print, falls fn 2 Parameter nimmt void print() { fn(1, 2); } }; auto fn = [](auto&& x) { std::cout << x; }; Printer<decltype(fn)>(fn).print(); auto fn2 = [](auto&& x, auto&& y) { std::cout << x << y; }; Printer<decltype(fn2)>(fn2).print();
-
Ist wahrscheinlich ein XY-Problem, daher solltest du eher beschreiben, was das ganze soll.
Das Problem besteht darin, dass Template Argumente erraten werden müssen, was natürlich nur in die Hose gehen kann, wenn das Lambda nur bestimmte Dinge annimmt (wovon auszugehen ist). Partial ordering ausnutzen ist unmöglich. Gut ist, dass es noch keine Concepts gibt, weswegen ein Parameter entweder alles nimmt oder einen gegebenen Typen hat.
Es gibt eine Annahme, die wir machen müssen, um die Arität halbwegs zuverlässig bestimmen zu können: Das Lambda hat einen trailing return type, der die Template Argumente nicht einschränkt (e.g.
void
). So muss der Rumpf nicht instantiiert werden.#include <type_traits> #include <utility> template <typename T> constexpr auto mem_op_arity = mem_op_arity<decltype(&T::operator())>; #define ARITY_REM_CTOR(...) __VA_ARGS__ #define ARITY_SPEC(cv, var) \ template <typename C, typename R, typename... Args> \ constexpr auto mem_op_arity<R (C::*) (Args... ARITY_REM_CTOR var) cv> = sizeof...(Args); ARITY_SPEC(const, (,...)) ARITY_SPEC(const, ()) ARITY_SPEC(, (,...)) ARITY_SPEC(, ()) #undef ARITY_SPEC namespace detail { struct arbitrary {template <typename T> operator T&&();}; template <std::size_t> arbitrary ignore; template <typename L, std::size_t... Is, typename U = decltype(std::declval<L>()(ignore<Is>...))> constexpr auto try_args(std::index_sequence<Is...>) {return sizeof...(Is);} template <std::size_t I, typename L> constexpr auto arity_temp(int) -> decltype(try_args<L>(std::make_index_sequence<I>{})) { return try_args<L>(std::make_index_sequence<I>{});} template <std::size_t I, typename L> constexpr std::enable_if_t<(I == 32), std::size_t> arity_temp(...) { return -1;} template <std::size_t I, typename L> constexpr std::enable_if_t<(I < 32), std::size_t> arity_temp(...) { return arity_temp<I+1, L>(0);} template <typename L, typename=decltype(&L::operator())> constexpr auto arity(int) {return mem_op_arity<L>;} template <typename L> constexpr auto arity(...) {return arity_temp<0, L>(0);} } template <typename L> constexpr std::size_t arity(L&& l) {return detail::arity<std::decay_t<L>>(0);} #include <iostream> int main() { std::cout << arity([] (auto a, int) -> void {std::cout << a.e;}); }
Geht auch auf VC++, mit kleinen Änderungen (weil der Compiler eben Dreck ist).
-
Ok, dein Code zählt auch auto Parameter. Lässt sich das jetzt noch so umbauen, dass es per enable_if das jeweilige print bei der Klasse in meinem zweiten Post aktiviert?
-
KN4CK3R schrieb:
Ok, dein Code zählt auch auto Parameter. Lässt sich das jetzt noch so umbauen, dass es per enable_if das jeweilige print bei der Klasse in meinem zweiten Post aktiviert?
//das print, falls fn 1 Parameter nimmt template <typename U> auto print_(U& fn) -> decltype(fn(1)) { return fn(1); } //das print, falls fn 2 Parameter nimmt template <typename U> auto print_(U& fn) -> decltype(fn(1, 2)) { return fn(1, 2); } auto print() {return print_(fn);}
-
Hm, so einfach...
Jetzt noch ein letzter Punkt dazu. In der Klasse befindet sich folgendes Typedef:
static typename TSource::value_type get_source(); static TFunction get_function(); typedef decltype(get_function()(get_source())) value_type; //typedef decltype(get_function()(get_source(), 0)) value_type; ???
TFunction ist T aus meinem Printer Beispiel. TSource::value_type ist der Typ des ersten Parameters der Funktion. Der zweite optionale Parameter ist ein size_t.
value_type soll der Typ des Rückgabewerts der übergebenen Funktion sein. Wie muss ich das schreiben, wenn die Funktion 1 oder 2 Parameter hat.
-
Wir könnten ein hübsches Makro schreiben:
#include <utility> #include <tuple> template <typename T, typename U> struct overload_t : T, U { using T::operator(); using U::operator(); }; template <typename T, typename... X> struct type_wrap {using type = decltype(std::declval<T>()(std::declval<X>()...));}; template <typename T, typename U, typename... X> type_wrap<overload_t<T, U>, X...> overload(std::tuple<X...>, T&&, U&&) {return {};} #define FIRST_(a, b) a #define SECOND_(a, b) b #define FIRST(x) FIRST_ x #define SECOND(x) SECOND_ x #define COUNT_(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) _10 #define COUNT(...) COUNT_(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define CONCAT_(a, b) a##b #define CONCAT(a, b) CONCAT_(a, b) #define LIST_0(...) #define LIST_1(M, x) M(x) #define LIST_2(M, x, y) M(x), M(y) #define LIST_3(M, x, y, z) M(x), M(y), M(z) #define LIST_4(M, x, ...) M(x), LIST_3(M, __VA_ARGS__) #define LIST_5(M, x, ...) M(x), LIST_4(M, __VA_ARGS__) #define LIST_6(M, x, ...) M(x), LIST_5(M, __VA_ARGS__) #define LIST_7(M, x, ...) M(x), LIST_6(M, __VA_ARGS__) #define LIST_8(M, x, ...) M(x), LIST_7(M, __VA_ARGS__) #define LIST_9(M, x, ...) M(x), LIST_8(M, __VA_ARGS__) #define LIST(M, ...) CONCAT(LIST_, COUNT(__VA_ARGS__))(M, __VA_ARGS__) #define DECLARE_PARAM(x) auto&& FIRST(x) #define PICK_VALID(e1, e2, ...) overload(std::forward_as_tuple(LIST(SECOND, __VA_ARGS__)), \ [] (LIST(DECLARE_PARAM, __VA_ARGS__)) -> decltype(e1) {}, \ [] (LIST(DECLARE_PARAM, __VA_ARGS__)) -> decltype(e2) {}) // Test: auto get_function() {return [] (int, int) {return "";};} auto o = PICK_VALID(f(1), f(1,1), (f, get_function())); static_assert(std::is_same<char const*, decltype(o)::type>{});
In deinem Fall also
auto somename1234 = PICK_VALID(f(get_source()), f(get_source(),1), (f, get_function())); using value_type = decltype(somename1234)::type;
Oder man bastelt sich langweiligerweise eine angepasste Variante
template <typename F, typename... T1s, typename T2> auto detect(F&& f, std::tuple<T1s...>&&, T2&&) -> decltype(f(std::declval<T1s>()...)); template <typename F, typename T1, typename... T2s> auto detect(F&& f, T1&&, std::tuple<T2s...>&&) -> decltype(f(std::declval<T2s>()...)); auto get_function() {return [] (int, int) {return "";};} using value_type = decltype(detect(get_function(), std::make_tuple(1), std::make_tuple(1, 1)));
(Lässt sich auf beliebig viele Argument Sequenzen ausweiten).
-
Die langweilige Variante gefällt mir, danke.
Bekommt man die beiden print_ Methoden auch noch irgendwie mit VS(2015) zum Laufen? (https://www.c-plusplus.net/forum/p2491013#2491013)
VS beschwert sich mitC2535 "unknown-type Printer_<T>::print_(U &)": Memberfunktion bereits definiert oder deklariert
Hier noch ein Link zum Onlinecompiler:
http://rextester.com/VCZAQ97017
-
Siehe "Workaround" in meiner Antwort auf SO: http://stackoverflow.com/a/28204207/3647361
Edit: Hier der Link: http://rextester.com/MLM54149
Habe lediglich ein, int = 0
angehängt.
-
perfekt, funktioniert!