decltype mit unbekannten Parametern als Parameter



  • Hallo,

    ich will einen Minimierungs-Algorithmus schreiben. Dabei habe ich folgendes Problem:

    template <typename T>
    struct min_options
    {
    	T tolerance; // Ein paar Optionen
    };
    
    template <typename cost_function>
    void minimize(cost_function &function_, min_options<decltype(function_(/* Problem */))> const &opts)
    {
    	// Hier Implementierung
    }
    
    double my_cost_function(std::vector<double>::iterator begin_, std::vector<double>::iterator end_)
    {
    	return 1.0; // Nur ein Beispiel
    }
    
    int main()
    {
    	min_options<double> my_options;
    	my_options.tolerance = 1.0e-9; // Toleranz festlegen
    
    	minimize(my_cost_function, my_options); // Minimiere "my_cost_function" mit optionen "my_options"
    
    }
    

    Die Funktion minimize soll ein Optionen-struct mitübergeben bekommen. Dieses soll sich korrekt selbst instanziieren, und zwar in Abhängigkeit des Rückgabewertes der übergebenen Kostenfunktion (hier: my_cost_function). Da in diesem Beispiel die Kostenfunktion double zurückgibt, soll der Aufruf von minimize double als template Parameter für das struct verwenden.

    Problem ist jetzt dass ich um decltype zu verwenden ja die genaue Funktionssignatur mit deren Typen kennen müsste (hier: std::vector<double>::iterator). Daher meine Frage, kann man sich nicht irgendwie einfach nur den Rückgabe-Typen einer Funktion holen, ohne dessen Funktionssignatur kennen zu müssen (wie bei decltype nötig)?


  • Mod

    min_options<typename std::result_of<cost_function>::type> const &opts
    

    ?

    Edit: Ist natürlich Unsinn - nimm

    min_options<typename std::function<cost_function>::result_type> const &opts
    


  • Arcoth schrieb:

    Edit: Ist natürlich Unsinn - nimm

    min_options<typename std::function<cost_function>::result_type> const &opts
    

    Sehr schön, das klappt 👍

    An std::function hab ich in diesem Zusammenhang gar nicht gedacht, vielen Dank 🙂



  • Hallo nochmal,

    es gibt doch noch ein Problem mit dieser Lösung, wie ich gerade festgestellt habe. Und zwar scheint das mit std::function nicht zu funktionieren wenn die übergebene Funktion ein lambda ist, also so:

    #include <vector>
    #include <functional>
    
    template <typename T>
    struct min_options
    {
        T tolerance; // Ein paar Optionen
    };
    
    template <typename cost_function>
    void minimize(cost_function &function_, min_options<typename std::function<cost_function>::result_type> const &opts)
    {
        // Hier Implementierung
    }
    
    double my_cost_function(std::vector<double>::iterator begin_, std::vector<double>::iterator end_)
    {
        return 1.0; // Nur ein Beispiel
    }
    
    int main()
    {
        min_options<double> my_options;
        my_options.tolerance = 1.0e-9; // Toleranz festlegen
    
     	using iter = std::vector<double>::iterator;
     	auto my_cost_lambda = [](iter begin_, iter end_) { return 1.0; };
    
        minimize(my_cost_function, my_options); // Geht 
        minimize(my_cost_lambda, my_options); // Geht nicht
    
    }
    

    Fehlermeldung ist "error: no matching function for call to ‘minimize(main()::__lambda0&, min_options<double>&)’"

    Kann man das noch so erweitern dass es für lambdas auch funktioniert?



  • Ich denke mir das etwa so:

    #include <vector>
    #include <functional>
    
    template<typename T> struct function_result {
      using type = typename function_result<decltype(&T::operator())>::type;
    };
    
    template<typename R, typename C, typename... A> struct function_result<R (C::*)(A...) const> { using type = R; };
    template<typename R, typename C, typename... A> struct function_result<R (C::*)(A...)      > { using type = R; };
    template<typename R,             typename... A> struct function_result<R       (A...)      > { using type = R; };
    template<typename R,             typename... A> struct function_result<R    (*)(A...)      > { using type = R; };
    
    template <typename T>
    struct min_options
    {
        T tolerance; // Ein paar Optionen
    };
    
    template <typename cost_function>
    void minimize(cost_function &, min_options<typename function_result<cost_function>::type> const &)
    {
        // Hier Implementierung
    }
    
    double my_cost_function(std::vector<double>::iterator, std::vector<double>::iterator)
    {
        return 1.0; // Nur ein Beispiel
    }
    
    int main()
    {
        min_options<double> my_options;
        my_options.tolerance = 1.0e-9; // Toleranz festlegen
    
        using iter = std::vector<double>::iterator;
        auto my_cost_lambda = [](iter, iter) { return 1.0; };
    
        minimize(my_cost_function, my_options); // Geht nicht
        minimize(my_cost_lambda, my_options); // Geht nicht
    }
    

    Da Lambdas ja einen operator() haben müssen, kann man den dafür missbrauchen. Allerdings wird das mit generalisierten Lambdas aus C++14 wohl nicht funktionieren, und andere Funktionsobjekte mit überladenem operator() wird das auch nicht abhandeln können.

    Um das Problem ganz allgemein aufzulösen, muss man den Funktionsaufruf etwas genauer spezifizieren (mit Parametern). Wir hatten ja gerade erst einen ganz ähnlichen Fall, dessen Ansatz wir weiterspinnen können. Etwa so:

    template<typename T> T &dummy_obj() { return *static_cast<T*>(0); }
    
    template <typename cost_function>
    void minimize(cost_function &, min_options<decltype(dummy_obj<cost_function>()(dummy_obj<std::vector<double>::iterator>(),
                                                                                   dummy_obj<std::vector<double>::iterator>()))> const &)
    {
        // Hier Implementierung
    }
    

  • Mod

    template<typename T> struct function_result {
      using type = typename function_result<decltype(&T::operator())>::type;
    };
    
    template<typename R, typename C, typename... A> struct function_result<R (C::*)(A...) const> { using type = R; };
    template<typename R, typename C, typename... A> struct function_result<R (C::*)(A...)      > { using type = R; };
    template<typename R,             typename... A> struct function_result<R       (A...)      > { using type = R; };
    template<typename R,             typename... A> struct function_result<R    (*)(A...)      > { using type = R; };
    

    Genau dasselbe wollte ich auch posten.

    Für operator() -Templates muss man natürlich die Argumente kennen, da der Rückgabetyp ja von diesen abhängen kann.

    Und warum dummy_obj anstatt std::declval ?



  • Weil ich declval gerade nicht auf dem Schirm hatte. Ist ein guter Einwand, sollte ersetzt werden.



  • seldon schrieb:

    Da Lambdas ja einen operator() haben müssen, kann man den dafür missbrauchen. Allerdings wird das mit generalisierten Lambdas aus C++14 wohl nicht funktionieren, und andere Funktionsobjekte mit überladenem operator() wird das auch nicht abhandeln können.

    Dass Lambdas gemäss Standard einen solchen Operator haben müssen wusste ich nicht. Du scheinst aber recht zu haben:

    N3337 [expr.prim.lambda] Klausel 5 schrieb:

    The closure type for a lambda-expression has a public inline function call operator (13.5.4) whose parameters
    and return type are described by the lambda-expression’s parameter-declaration-clause and trailing
    return-type respectively. This function call operator is declared const (9.3.1) if and only if the lambda-
    expression’s parameter-declaration-clause is not followed by mutable. It is neither virtual nor declared
    volatile. Default arguments (8.3.6) shall not be specified in the parameter-declaration-clause of a lambda-
    declarator. Any exception-specification specified on a lambda-expression applies to the corresponding function
    call operator. An attribute-specifier-seq in a lambda-declarator appertains to the type of the corresponding
    function call operator. [ Note: Names referenced in the lambda-declarator are looked up in the context in
    which the lambda-expression appears. —end note ]

    Interessant finde ich auch die darauffolgende Klausel:

    N3337 [expr.prim.lambda] Klausel 6 schrieb:

    The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const
    conversion function to pointer to function having the same parameter and return types as the closure type’s
    function call operator. The value returned by this conversion function shall be the address of a function
    that, when invoked, has the same effect as invoking the closure type’s function call operator.

    Das wusste ich auch nicht. Damit und mit SFINAE (und mit Klausel 3) lässt sich doch ein ziemlich gutes is_closure für capturefreie Closures schreiben (zumindest in C++11).
    Ausserdem sind diese Klauseln in neueren WDs (z.B. N3690) noch interessanter wegen der Generizität.

    Achja, du hast übrigens die Spezialisierung von function_result für variadische Varianten vergessen. Dazu einfach bei allen Versionen (A...) durch (A..., ...) ersetzen.

    Edit: Bei MSVC wurden diese Details natürlich nicht korrekt implementiert. 🙄



  • Hm, das wird komplizierter als gedacht 😃

    Danke auf jeden Fall für die Antworten, wer mal weiter rumprobieren ob ich das so implementiert bekomme (wobei ich wenn ich mir den letzten Post anschaue befürchte dass VS da mal wieder buggy ist...)


Anmelden zum Antworten