UnaryFunction in c++ 17


  • Mod

    Arcoth schrieb:

    Was soll überhaupt dieses static_assert ? Es hat keinen Einfluss auf overload resolution und ist im Falle eines falschen Iteratortypen sowieso überflüssig.

    Hatte ich doch schon eine Seite vorher erklärt? Placebo um sich besser zu fühlen.



  • Ja aber das problem ist doch auch dass ich am Kompilierungsfehler nicht sehe wer der Aufrufer ist der den Fehler verursacht.

    Gebe ich zum beispiel nun einen iterator von strings in die Funktion Add erhalte ich den Fehler:

    GetName': is not a member of 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'

    hier ist kein Bezug zum Aufrufer!



  • Verstehe ich das richtig enable if ist dafür da dass ich z.B. zwischen zwei Überladungen steuern will.

    wenn ich z.b. einmal einen string oder einen shared_ptr<MyVariable> als Iter typ rein geben will

    template <typename Iter, typename = std::enable_if_t<std::is_same_v<string>, std::iterator_traits<Iter> >>
    void Add(Iter first, Iter last);
    
    template <typename Iter, typename = std::enable_if_t<std::is_same_v<shared_ptr<MyVariable>, std::iterator_traits<Iter>>>>
    void Add(Iter first, Iter last);
    

    Wenn ich das aber mache erhalte ich den Fehler dass Add schon deklariert ist.
    Ich mache wahrscheinlich genau den Fehler der hier beschrieben ist

    http://en.cppreference.com/w/cpp/types/enable_if

    "A common mistake is to declare two function templates that differ only in their default template arguments"

    Aber irgendwie weiß ich jetzt nicht wie anders machen.


  • Mod

    booster schrieb:

    Ja aber das problem ist doch auch dass ich am Kompilierungsfehler nicht sehe wer der Aufrufer ist der den Fehler verursacht.

    Gebe ich zum beispiel nun einen iterator von strings in die Funktion Add erhalte ich den Fehler:

    GetName': is not a member of 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'

    hier ist kein Bezug zum Aufrufer!

    Die vollständige Fehlermeldung sagt in diesen Fällen auch immer von wo aus eine Instantiierung erfolt ist.
    Wenn du eine IDE benutzt, die nur die erste Zeile von Fehlermeldungen auflistet, wirst du den rohen Output des Compilers betrachten müssen.

    booster schrieb:

    template <typename Iter, typename = std::enable_if_t<std::is_same_v<string>, std::iterator_traits<Iter> >>
    void Add(Iter first, Iter last);
    
    template <typename Iter, typename = std::enable_if_t<std::is_same_v<shared_ptr<MyVariable>, std::iterator_traits<Iter>>>>
    void Add(Iter first, Iter last);
    

    Wenn ich das aber mache erhalte ich den Fehler dass Add schon deklariert ist.
    Ich mache wahrscheinlich genau den Fehler der hier beschrieben ist

    http://en.cppreference.com/w/cpp/types/enable_if

    "A common mistake is to declare two function templates that differ only in their default template arguments"

    Aber irgendwie weiß ich jetzt nicht wie anders machen.

    Statt enable_if als Defaultargument zu verwenden, kann es auch Bestandteil des Typs eines nontype Templateparameters sein. z.B.

    template <typename Iter, typename std::enable_if_t<std::is_same_v<std::string, std::iterator_traits<Iter>::value_type>, int> = 0>
    void Add(Iter first, Iter last);
    
    template <typename Iter, typename std::enable_if_t<std::is_same_v<std::shared_ptr<MyVariable>, std::iterator_traits<Iter>::value_type>, long> = 0>
    void Add(Iter first, Iter last);
    

    Wobei es in Fällen wie diesen, wo die Liste der möglichen Überladungen nicht nachträglich von aussen erweitert werden soll, ggf. sinnvoll ist, if constexpr einzusetzen

    template <typename Iter>
    void Add(Iter first, Iter last)
    {
        if constexpr (std::is_same_v<std::string, std::iterator_traits<Iter>::value_type>)
        ...
        else if constexpr (std::is_same_v<std::shared_ptr<MyVariable>, std::iterator_traits<Iter>::value_type>)
        ...
        else
            static_assert(false);
    }
    


  • muss es nicht

    template <typename Iter, std::enable_if_t<std::is_same_v<std::string, typename std::iterator_traits<Iter>::value_type>, int> = 0>
    

    heißen?

    also typename vor std::enable_if_t weg
    dafür aber typename vor std::iterator_traits hin?


  • Mod

    booster schrieb:

    muss es nicht

    template <typename Iter, std::enable_if_t<std::is_same_v<std::string, typename std::iterator_traits<Iter>::value_type>, int> = 0>
    

    heißen?

    also typename vor std::enable_if_t weg
    dafür aber typename vor std::iterator_traits hin?

    richtig.



  • ich raff das ganze noch nicht

    wieso im Fall "string" int als parametertyp
    und im Fall shared_ptr<MyVariable> long?

    wenn ich nun deinen Code hernehme und folgendes aufrufe:

    set<string> names = {"Var1", "Var2" } ;
    Add(names.begin(), names.end());
    

    erhalte ich den Fehler:
    "no overloaded function takes 2 arguments"



  • sorry. das war falsch. jetzt würde ich den letzen Beitrag auch löschen wenn ich könnte 🙂

    Mache ich folgendes:

    template <typename Iter, std::enable_if_t<std::is_same_v<std::string, typename std::iterator_traits<Iter>::value_type>, int> = 0>
            void Add(Iter first, Iter last)
    {
         // mach nicht viel sinn aber fürs beispiel halt.
         const std::set<string> names(begin(first), end(last));
    }
    
    set<string> names = {"Var1", "Var2" } ;
    Add(names.begin(), names.end());
    

    erhalte ich den Fehler

    'begin': no matching overloaded function found

    Weil er jetzt davon ausgeht das Iter vom typ int ist ?!?

    ändere ich nun int in string

    template <typename Iter, std::enable_if_t<std::is_same_v<std::string, typename std::iterator_traits<Iter>::value_type>, string> = 0>
            void Add(Iter first, Iter last)
    

    erhalte ich den vorher besagten Fehler
    "no overloaded function takes 2 arguments"


  • Mod

    booster schrieb:

    wieso im Fall "string" int als parametertyp
    und im Fall shared_ptr<MyVariable> long?

    Gewohnheit. iirc hatte gcc früher mal Probleme die Namen von Spezialisierungen unterschiedlich zu mangeln, und so die Templates auseinanderzuhalten, wenn enable_if stets den gleichen Typ liefert.


  • Mod

    const std::set<string> names(begin(first), end(last));
    

    Was soll begin(first) sein?
    first ist doch schon ein Iterator.



  • Was soll begin(first) sein?

    Äh natürlich.

    const std::set<string> names(first, last);
    

    dann machst mehr Sinn 🙂

    iirc hatte gcc früher mal Probleme die Namen von Spezialisierungen unterschiedlich zu mangeln, und so die Templates auseinanderzuhalten, wenn enable_if stets den gleichen Typ liefert.

    ist das nicht das Problem auf das ich schon hingewiesen habe?
    "A common mistake is to declare two function templates that differ only in their default template arguments"

    Aber nochmals. Du setzt hier einfach 2 verschiedene typen dass das Template richtig erkannt wird?

    Wieso funktioniert dann statt int nicht auch string bzw. im anderen Fall shared_ptr<MyVariable>


  • Mod

    booster schrieb:

    Aber nochmals. Du setzt hier einfach 2 verschiedene typen dass das Template richtig erkannt wird?

    Du kannst gerne immer int nehmen, hier geht es um ein Problem, das in modernen Compilern schon lange gelöst ist.

    booster schrieb:

    ist das nicht das Problem auf das ich schon hingewiesen habe?
    "A common mistake is to declare two function templates that differ only in their default template arguments"

    meint dieses:

    //        Parameter   Parameter     Defaultargument
    //        --------    --------   ------------------------
    template <typename T, typename = std::enable_if_t<abc...>>
    void f(T);
    
    //        Parameter   Parameter     Defaultargument
    //        --------    --------   ------------------------
    template <typename T, typename = std::enable_if_t<xyz...>>
    void f(T);
    

    Die Templateparameterlisten beider Deklarationen sind gleich (jeweils <typename,typename>), ebenso die Funktionsparameter, mithin sind die Signaturen beider Templates gleich und eine Überladung ist nicht möglich. Die Tatsache, dass die Defaultargumente unterschiedlich sind, spielt keine Rolle.

    Das ist ganz Analog zu Defaultfunktionsargumenten:

    void f(int x, int y = 1);
    void f(int x, int y = 1); // error defaultargument nochmals angegeben
    void f(int x, int y = 2); // error defaultargument nochmals angegeben und ausserdem anders
    


  • aha. ja dann brauche ich ja aber auch kein int angeben. dann kann ich ja auch void nehmen und das ist sowieso schon default?

    also so?

    template <typename Iter, std::enable_if_t<std::is_same_v<std::string, typename std::iterator_traits<Iter>::value_type>> = 0>



  • aber dann erhalte ich wieder den Fehler. "no overload excepts 2 params"
    nur mit int gehts???


  • Mod

    booster schrieb:

    aha. ja dann brauche ich ja aber auch kein int angeben. dann kann ich ja auch void nehmen und das ist sowieso schon default?

    also so?

    template <typename Iter, std::enable_if_t<std::is_same_v<std::string, typename std::iterator_traits<Iter>::value_type>> = 0>

    void ist kein Objekttyp, und kann deshalb nicht der Typ eines Templatearguments sein.

    void x = void{};
    

    geht ja auch nicht.



  • Klingt logisch 🙂

    Jetzt hänge ich nur noch an deiner Aussage "in den meisten Fällen reicht eine constexpression.

    Eine if else abfrage ist einer Überladung vorzuziehen. Echt jetzt?


  • Mod

    booster schrieb:

    Klingt logisch 🙂

    Vor C++11 hat man auch gerne void* eingesetzt:

    template <typename T>
    void foo(T, typename enable_if<condition>::type*=0);
    

    Allerdings haben wir jetzt nullptr, und dieses hier zu verwenden führt zu mehr Schreibaufwand als int.

    booster schrieb:

    Eine if else abfrage ist einer Überladung vorzuziehen. Echt jetzt?

    Echt jetzt.
    if constexpr wertet einen konstanten Ausdruck aus und instantiiert nur den Zweig der Bedingung, der auch tatsächlich ausgeführt wird. Damit lassen sich u.a. unötige Spezialisierungen und Codeduplikation vermeiden.
    Klassisch:

    template <int I>
    int fak() {
        if (I==0)
            return 1;
        else
            return I*fak<I-1>();
    }
    ...
    fak<0>();
    

    Das kompiliert nicht. Obwohl der Compiler weiss, dass für I==0 die Alternative I*fak<I-1>() nicht ausgeführt wird, muss dieser Teil trotzdem instantiiert werden. Was zu unbegrenzter rekursiver Instantiierung führen muss.
    Deshalb musste hier bisher statt dessen spezialisiert werden.
    Mit if constexpr können wir einfach schreiben

    template <int I>
    int fak() {
        if constexpr (I==0)
            return 1;
        else
            return I*fak<I-1>();
    }
    

    und sind fertig.


Anmelden zum Antworten