std::set und std::less -> 3 overloads have similar conversions



  • @Th69 sagte in std::set und std::less -> 3 overloads have similar conversions:

    Warum hast du denn überhaupt die 3 verschiedenen operator() definiert (also warum willst du beliebige Datentypen mit std::string vergleichen)?

    Weil er set.find("blub") machen möchte, mit einem set das derived Objekte speichert. Das macht schon oft Sinn.



  • @hustbaer sagte in std::set und std::less -> 3 overloads have similar conversions:

    Weil er set.find("blub") machen möchte, mit einem set das derived Objekte speichert. Das macht schon oft Sinn.

    Richtig das habe ich ja in der main auch angedeutet. Sinn dahinter dass ich mir die doppelte haltung des keys im gegensatz zu einer map sparen kann.

    @hustbaer sagte in std::set und std::less -> 3 overloads have similar conversions:

    Ich denke dass du deine operator() Templates auf Typen einschränken musst die nicht von base abgeleitet sind.

    Ja das habe ich auch schon gedacht. Ich weiß nicht ob das so richtig ist

    template <typename T, class = std::enable_if_t<!std::is_base_of_v<base, T>>> 
    bool operator()(const base& rhs, T&& y) const { return rhs.Name < std::forward<T>(y); }
    

    Aber wenn ich das so mache erhalte ich immer noch genau den selben Fehler!



  • Und warum benutzt er dann nicht direkt Überladungen für std::string und const char *?

    Klar kann man mit SFINAE (std::enable_if) bzw. (seit C++20) mit Constraints and concepts die Typen einschränken, aber ob das den Aufwand wert ist?



  • @Th69 sagte in std::set und std::less -> 3 overloads have similar conversions:

    Und warum benutzt er dann nicht direkt Überladungen für std::string und const char *?

    Du darfst mich auch gerne direkt ansprechen. 🙂
    Da das nur ein Beispiel war und ich die Compare Funktion etwas allgemeiner spezifizieren wollte, und ich sonst noch weitere Überladungen bräuchte ausser string.

    @Th69 sagte in std::set und std::less -> 3 overloads have similar conversions:

    Klar kann man mit SFINAE (std::enable_if) bzw. (seit C++20) mit Constraints and concepts die Typen einschränken, aber ob das den Aufwand wert ist?

    Wie ich zuletzt geschrieben habe hatte ich das so schon versucht:

    template <typename T, class = std::enable_if_t<!std::is_base_of_v<base, T>>>
    bool operator()(const base& rhs, T&& y) const { return rhs.Name < std::forward<T>(y); }
    


  • Sorry, ich dachte ich würde direkt auf @hustbaer's Beitrag antworten.
    Schon wieder wurden mir Beiträge (hier also deiner von vor 2h) nicht im offenen Tab angezeigt...

    Es scheint ja trotzdem operator()(const base& lhs, const base& rhs) immer zur Konvertierung herangezogen zu werden, weil dann wohl mittels base(std::string name) ein temp. base-Objekt erzeugt werden kann (wobei das eigentlich 2 Konvertierungen sind, und dann besonders noch eine explicit).

    Was passiert denn, wenn du nur diesen Operator definierst?



  • @Th69 sagte in std::set und std::less -> 3 overloads have similar conversions:

    Was passiert denn, wenn du nur diesen Operator definierst?

    Error        C2664        'bool base::compare::operator ()(const base &,const base &) const': cannot convert argument 2 from 'const char [5]' to 'const base &'
    

  • Mod

    Folgendes ist ebenfalls ill-formed (ohne derived ueberhaupt zu involvieren):

        base b{"d"};
        base::compare{}(b, b);
    

    Intuitiv wuerde man erwarten, dass der non-template overload bevorzugt wird. Allerdings hast Du ueberfluessigerweise deine Parameter als non-const deklariert, was folgende Regel greifen laesst

    S1 and S2 include reference bindings ([dcl.init.ref]), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

    Das perfect forwarding ist auch vollkommen ueberfluessig, weil ein Vergleichsoperator nicht von seinem Argument moved (ausser er nimmt by-value, in welchem Fall der Typ keine teure Kopie hat). Mit einer const-ref kompiliert zumindest schonmal obiges Beispiel.

    Um dann deine main Funktion wohlgeformt zu machen, muss man wie bereits erwaehnt, den Templateparameter beschraenken, weil die derived-to-base conversion bedeutet, dass die non-template Funktion ein schlechterer Match ist, waehrend die Template-Funktionen untereinander nicht unterscheidbar sind. (Die Funktionstemplates sind aber beide ein besserer match in diesem speziellen Fall.) Du koenntest aber auch einfach ein string_view nehmen?

    @Th69 sagte in std::set und std::less -> 3 overloads have similar conversions:

    wobei das eigentlich 2 Konvertierungen sind, und dann besonders noch eine explicit

    "besonders noch eine explicit"? Das spielt fuer overload resolution ueberhaupt keine Rolle, sondern nur fuer die candidate sets.



  • Hallo Columbo

    Also der comparer wurde mir mal vor längerem hier im Forum gepostet. Ja man darf nicht alles glauben. 😃
    Aber woher soll ich nun immer wissen wer recht hat,

    string_view würde natürlich gehen. Aber wie gesagt es ist nur ein Beispiel. Es ist nicht immer ein string oder etwas der gleichen. Darum das allgemeine über T.

    Wie gesagt das über die Einschränkung der Template Parameter habe ich ja versucht hat aber so nicht geklappt.
    Darum die Frage wie die Einschränkung richtig aussehen sollte.



  • So: Ideone-Code (also wie @Columbo schon geschrieben hat, mit const T&).

    Habe auch noch den virtuellen Destruktor hinzugefügt.

    PS: Ideone kennt z.Z. nur C++14, daher kein std::enable_if_v.



  • @booster sagte in std::set und std::less -> 3 overloads have similar conversions:

    Aber woher soll ich nun immer wissen wer recht hat,

    Ganz einfach: wenn @Columbo was zum Thema C++ schreibt, hat er meistens Recht 😉


  • Mod

    @booster Du kannst auch ein Template schreiben, dass die gesamte Logik einfach kapselt:

    template <auto MemPtr, typename UnderlyingComp = std::less<>>
    class MemberComparator : UnderlyingComp {
        template <typename T>
        static constexpr decltype(auto) obtain(T&& c) {
            if constexpr (requires{c.*MemPtr;}) {
                return std::forward<T>(c).*MemPtr;
            } else {
                return std::forward<T>(c);
            }
        }
        
    public:
        // Edit: using UnderlyingComp::is_transparent ist fast besser.
        using is_transparent = int;
    
        template<typename T, typename U> 
        constexpr decltype(auto) operator()(T&& lhs, U&& rhs) const {
            return UnderlyingComp::operator()(
                obtain(std::forward<T>(lhs)), obtain(std::forward<U>(rhs)));
        }
    };
    

    Dann sieht das einfach so aus:

        std::set<derived, MemberComparator<&derived::Name>> baseset; 
        baseset.emplace("Test"); 
        auto x = baseset.find("Test"); 
    

    Demo.

    Man kann obtain auch durch Overload{std::bind(MemPtr, std::placeholders::_1), std::identity{}} definieren wenn man pervers ist (siehe hier).

    Es scheint uebrigens, dass ich mein Urteil über perfect forwarding revidieren muss. cpprefence schreibt:

    The member type is_transparent indicates to the caller that this function object is a transparent function object: it accepts arguments of arbitrary types and uses perfect forwarding, which avoids unnecessary copying and conversion when the function object is used in heterogeneous context, or with rvalue arguments.

    Diese Gruende sind schwach. Generisches const-ref bindet auch an rvalues ohne je eine Kopie zu erstellen. "Heterogenous context" ist ein bisschen vage, aber ich nehme an, der Autor bezog sich auf einen Aufruf mit zwei unterschiedlichen Typen, was nichts mit dem Parametertyp eines einzelnen Parameters, sondern mit der gegenseitigen Unabhaengigkeit beider Parametertypen zusammenhaengt.

    Ich stimme aber zu, dass es maximal generisch/korrekt ist, fuer einen library comparator wie std::less perfect forwarding einzusetzen, einfach um die legalen Moeglichkeiten abzudecken, dass der letztendlich aufgerufene Comparator irgendwie nur rvalues, oder nicht-triviale Typen by-value nimmt. Deshalb auch perfect forwarding in der obigen Loesung. Man muss den Leuten ja erlauben, schwachsinnig zu designen. 🤪

    @hustbaer sagte in std::set und std::less -> 3 overloads have similar conversions:

    Ganz einfach: wenn @Columbo was zum Thema C++ schreibt, hat er meistens Recht 😉

    Ich wuenschte es waere so.... ich habe aber in den letzten vier Jahren nur mit C++ gearbeitet und die Theorie vollkommen vernachlässigt. Zum Glueck bin ich ab Juni auf einem Sabbatical.



  • @Columbo
    Also ich kann mich nicht erinnern beobachtet zu haben dass du in letzter Zeit oft eine nicht-eingeschränkte, faktische Aussage zu C++ gemacht hättest, die dann falsch war.

    Deine obtain Funktion ist nett, da sieht man schön wie einfach sowas jetzt mit modernem C++ sein kann. Leider sind wir aus technischen Gründen noch auf C++17 eingeschränkt (Solaris 10 Support). Aber wenigstens ist es schon C++17, vor ~4 Jahren waren wir noch auf C++98 😨



  • @Columbo

    Ok. Jetzt raucht mir der Kopf. Wo ich bei dir bin ist das Sabbatical. Das kann ich auch 😉

    Dein Code geht bei mir auch nicht da ich auch nur c++17 kann.


  • Mod

    @booster sagte in std::set und std::less -> 3 overloads have similar conversions:

    Dein Code geht bei mir auch nicht da ich auch nur c++17 kann.

    Der Code laesst sich auch leicht C++17-kompatibel machen; https://coliru.stacked-crooked.com/a/e1355db04f7cb26a


Log in to reply