UnaryFunction in c++ 17


  • Mod

    Genaugenommen funktioniert die optimierte Variante oben sicher nur für Forward Iteratoren (oder besser).
    Falls *it ein prvalue ist (wie bei InputIteratoren möglich), führt

    auto&& var = f(*begin++);
    

    nicht zur gewünschten Lebenszeitverlängerung des temporären Objektes, falls es von f nur weitergereicht wird.

    Besser ist daher:

    template <typename InputIter, typename Func>
    void Add(InputIter begin, InputIter end, Func f)
    {
        static_assert(std::is_same_v<std::remove_reference_t<decltype(f(*begin))>, decltype(_varMap)::mapped_type>);
        decltype(_varMap) tmp;
        for ( ; begin != end; )
        {
            // if *begin is a prvalue and f(*begin) a glvalue, that glvalue might be refering to that materialized prvalue,
            // we then must copy/move the result. otherwise capture by reference.
            using var_t = std::conditional_t<std::is_reference_v<decltype(f(*begin))> && !std::is_reference_v<decltype(*begin)>,
                                             std::remove_reference_t<decltype(f(*begin))>, decltype(f(*begin))&&>;
            var_t var = f(*begin++);
            if (_varMap.find(var->GetName()) != _varMap.end())
                return;
            tmp.try_emplace(var->GetName(), std::forward<var_t>(var));
        }
        _varMap.merge(tmp);
    }
    

    Das sieht schon nicht mehr so schön aus 😞



  • @camper

    du machst mich fertig.

    Also genau genommen funtkioniert die erste optimierte Variante schon mal in meinem Fall nicht!


  • Mod

    "funktioniert nicht" ist keine Fehlerbeschreibung.



  • @camper

    "funktioniert nicht" ist keine Fehlerbeschreibung.

    du hast vollkomen recht. Sorry.
    Aber so wie es aussieht lag es nur am neu komilieren. Visual Studio hat mir beim debuggen nach dem Aufruf von

    auto var = func(*first++);

    angezeigt im watch dass var leer war

    var {...}
    

    hat sich auch nicht aufklappen lassen.


  • Mod

    Das static_assert sollte besser auch noch cv-Qualifikationen entfernen:

    static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<decltype(f(*begin))>>, decltype(_varMap)::mapped_type>);
    

  • Mod

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


  • 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???


Anmelden zum Antworten