template in Abhängigkeit der Anzahl der Argumente?



  • Hallo,

    ich wollte mal fragen ob sowas möglich ist:

    template <typename func>
    void foo(func f)
    {
    	std::vector<int> v(3);
    
    	if (/* func ist Funktion vom Typ f1 bzw. nimmt 2 Argumente */) {
    		f(std::begin(v), std::end(v));
    	}
    	else /* func ist Funktion vom Typ f2 bzw. nimmt 1 Argument */ { 
    		f(v);
    	}
    }
    
    void f1(std::vector<int>::iterator begin_, std::vector<int>::iterator end_)
    {
    	// mach irgendwas
    }
    
    void f2(std::vector<int> const &vector_)
    {
    	// mach irgendwas
    }
    
    int main()
    {
    	foo(f1);
    	foo(f2);
    }
    

    Ziel wäre dass der Benutzer von foo seine Funktionen entweder wie f1 oder wie f2 deklarieren kann, also das template davon abhängt ob die Funktion 1 Argument oder 2 nimmt.

    Wie man die Anzahl der Funktionsparameter zur Compilezeit mittels templates zählen kann weiß ich schon, aber wie kann man sich dann das nötige if-Konstrukt bauen? Geht das überhaupt?


  • Mod

    template <typename func>
    auto foo_call(std::vector<int> const& v, func f) -> decltype(f(v)) {return f(v);}
    
    template <typename func>
    auto foo_call(std::vector<int> const& v, func f) -> decltype(f(v.begin(), v.end())) {return f(v.begin(), v.end());}
    
    template <typename func>
    void foo(func f)
    {
        std::vector<int> v(3);
        foo_call(v, f);
    }
    

    ggf. mit einem AUTO_RETURN -Makro verkürzen.



  • Hallo,

    Arcoth schrieb:

    template <typename func>
    auto foo_call(std::vector<int> const& v, func f) -> decltype(f(v)) {return f(v);}
    
    template <typename func>
    auto foo_call(std::vector<int> const& v, func f) -> decltype(f(v.begin(), v.end())) {return f(v.begin(), v.end());}
    
    template <typename func>
    void foo(func f)
    {
        std::vector<int> v(3);
        foo_call(v, f);
    }
    

    ggf. mit einem AUTO_RETURN -Makro verkürzen.

    wie soll das funktionieren, die Funktionen unterscheiden sich ja nur hinsichtlich des Rückgabetypes, welcher ja nicht für die Überladung berücksichtigt wird.

    Auf jeden Fall erhalte ich mit diesem Code einen Fehler:

    template <typename func>
    auto foo_call(std::vector<int> const& v, func f) -> decltype(f(v)) { return f(v); }
    
    template <typename func>
    auto foo_call(std::vector<int> const& v, func f) -> decltype(f(v.begin(), v.end())) { return f(v.begin(), v.end()); }
    
    template <typename func>
    void foo(func f)
    {
    	std::vector<int> v(3);
    	foo_call(v, f);
    }
    
    int f1(std::vector<int> const &v_) { std::cout << "f1\n"; }
    int f2(std::vector<int>::iterator begin_, std::vector<int>::iterator end_) { std::cout << "f2\n"; }
    
    int main()
    {
    	foo(f1); // Fehler, gleiches mit f2
    }
    

    error C2995: "unknown-type foo_call(const std::vector<int,std::allocator<_Ty>> &,func)": Funktionsvorlage wurde bereits definiert.



  • Ja, der MSVC hat das noch nicht so richtig raus. In C++03 könnte man das etwa so machen:

    #include <cstddef>
    #include <iostream>
    #include <vector>
    
    // Dummy-Typ für SFINAE. 
    template<std::size_t> struct dummy_type { };
    
    // Syntaxzucker. Darf nicht wirklich verwendet werden, aber das Zeug in sizeof-Ausdrücken wird halt
    // nicht ausgewertet, und es sieht so etwas lesbarer aus als überall direkt die *static_cast<T*>(0)
    // zu haben.
    template<typename T> T &dummy_obj() { return *static_cast<T*>(0); }
    
    // Hier SFINAE-Trick: Wenn f(v) nicht kompilierbar ist, ist es auch dummy_obj<func>()(dummy_blablabla) nicht,
    // und dann kann dummy_type<sizeof(blabla)> nicht substituiert werden. Damit fällt die Spezialisierung aus dem
    // Lookup und wird nicht weiter beachtet.
    template <typename func>
    void foo_call(std::vector<int> const& v, func f,
                  dummy_type<sizeof(dummy_obj<func>()(dummy_obj<std::vector<int> const>()),0)>* = 0)
    {
      f(v);
    }
    
    // Hier das selbe in grün.
    template <typename func>
    void foo_call(std::vector<int> const& v, func f,
                  dummy_type<sizeof(dummy_obj<func>()(dummy_obj<std::vector<int>::const_iterator>(),
                                                      dummy_obj<std::vector<int>::const_iterator>()),0)>* = 0)
    {
      f(v.begin(), v.end());
    }
    
    template <typename func>
    void foo(func f)
    {
        std::vector<int> v(3);
        foo_call(v, f);
    }
    
    void f1(std::vector<int> const &v_) { std::cout << "f1\n"; }
    void f2(std::vector<int>::const_iterator begin_, std::vector<int>::const_iterator end_) { std::cout << "f2\n"; }
    
    int main()
    {
        foo(f1);
        foo(f2);
    }
    

    In dieser Form führt das noch zu Fehlern, wenn ein Funktionsobjekt beide Signaturen unterstützt. Das ließe sich ggf. mit enable_if auflösen, ist aber dann mehr Aufwand.



  • happystudent schrieb:

    Hallo,

    Arcoth schrieb:

    template <typename func>
    auto foo_call(std::vector<int> const& v, func f) -> decltype(f(v)) {return f(v);}
    
    template <typename func>
    auto foo_call(std::vector<int> const& v, func f) -> decltype(f(v.begin(), v.end())) {return f(v.begin(), v.end());}
    
    template <typename func>
    void foo(func f)
    {
        std::vector<int> v(3);
        foo_call(v, f);
    }
    

    ggf. mit einem AUTO_RETURN -Makro verkürzen.

    wie soll das funktionieren, die Funktionen unterscheiden sich ja nur hinsichtlich des Rückgabetypes, welcher ja nicht für die Überladung berücksichtigt wird.

    Die jeweils andere Funktion sollte durch SFINAE aus der overload resultion gezogen werden. Bitte schlagt mich nicht, wenn das nicht stimmt, ist so früh 😉



  • Ok, dann werd ich das mal so ausprobieren. Danke auf jeden Fall für die Antworten 👍


Anmelden zum Antworten