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

  • Mod

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

    Die Ausgabe ist 2.

    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?


  • Mod

    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?

    Überflüssig:

    //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.


  • Mod

    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 mit

    C2535	"unknown-type Printer_<T>::print_(U &)": Memberfunktion bereits definiert oder deklariert
    

    Hier noch ein Link zum Onlinecompiler:
    http://rextester.com/VCZAQ97017


  • Mod

    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!


Log in to reply