Selbstgebautes printf und Perfec Forwarding



  • Heyo!

    Manchmal zeige ich ja das printf über Variadic Templates her. Ich habe mich gerade gefragt, ob man nicht einfach alle Argumente per const& nehmen könnte. Übersehe ich was?

    Grüße,
    PI



  • Ich glaube, mein Anliegen war zu ungenau. Hier mal ein Stückchen Code:

    void printf(char const* format);
    
    template <typyename Head, typename... Tail>
    void printf(char const* format, Head&& head, Tail&&... tail); // Head const&, Tail const&... ?
    


  • 314159265358979 schrieb:

    Ich glaube, mein Anliegen war zu ungenau.

    Es fehlt die Frage.

    Implementier das printf einfach mal, dann wirst du ja sehen ob es irgendwo Probleme gibt oder stell eine genauere Frage.



  • Ich dachte ihr kennt mein printf doch bereits, das ich jedem printf-Freund unter die Nase reibe. Naja gut, bitte:

    void printf(char const* format)
    {
        std::cout << format;
    }
    
    // Version 1
    template <typename Head, typename... Tail>+
    void printf(char const* format, Head&& head, Tail&&... tail)
    {
        while(*format != '%')
            std::cout << *format++;
    
        std::cout << head;
        printf(++format, std::forward<Tail>(tail)...);
    }
    
    // Version 2
    template <typename Head, typename... Tail>
    void printf(char const* format, Head const& head, Tail const&... tail)
    {
        while(*format != '%')
            std::cout << *format++;
    
        std::cout << head;
        prtinf(++format, tail...);
    }
    

    Gibt es einen Grund, warum man Version 1 anstatt Version 2 nehmen sollte?



  • Die erste Variante würde doch nach der aktuellen Version für rvalue-Refs folgendes gar nicht mehr erlauben, oder?

    float pi = std::acos(-1.f);
    printf(pi); //sollte mit der 1. Version gar nicht mehr kompilieren
    


  • Doch, funktioniert. Wenn du einen int& übergibst, wird T zu int&, int& + && = int&.
    Das ist der Grund, warum auch Perfect forwarding funktioniert. Falls T = U& war T ein LValue. Falls T = U wars ein RValue.



  • Ah ja, ich seh' es. Für Templateargumente gibts eine Spezialregel. örks.



  • && und std::forward sind hier eigentlich überflüssig. const& tut es auch. Du weißt ja ganz genau, dass Du nix ändern sondern nur anzeigen willst.

    Außerdem:
    - %% klappt nicht so wie man das erwarten würde
    - wenn zuviele Argumente gegeben sind, läufst Du über das Stringende hinaus.



  • Tachyon schrieb:

    Ah ja, ich seh' es. Für Templateargumente gibts eine Spezialregel. örks.

    Ja, eine Inkonsistenz, die sicherlich noch vielen zu schaffen machen wird. Vielleicht hätte man für Perfect Forwarding versuchen sollen, eine andere Lösung zu finden...



  • Ich habe sogar mal einen Vorschlag für eine Syntax gesehen, über die man diese "komische" Deduktionsregel explizit einschalten würde:

    template<class  T> void foo(T &&);
    template<class& T> void bar(T &&);
    
    int main()
    {
      int i = 42;
      foo(9); // --> T=int,  OK
      foo(i); // --> T=int,  Compilezeitfehler, da lvalue argument
      bar(9); // --> T=int,  OK
      bar(i); // --> T=int&, OK
    }
    

    "class& T" = T wird ggf als lvaluereferenz-typ deduziert.

    Aber dafür ist es jetzt schon viel zu spät...



  • Wie sieht denn die momentane Vorgehensweise aus, wenn man nur RValues will? enable_if mit is_rvalue_reference ?



  • @kruemelkacker: Die Probleme der Funktion sind mir durchaus bewusst. Es ging mir erstmal nur um die Frage "Perfect Forwarding oder const&?".



  • Ich finde, dass const-Ref hier besser die Realität abbildet. Du willst ja nicht forwarden, sondern Lesezugriff auf die Parameter haben.



  • Was ist, wenn der operator << überladen ist und z.B. einen T&& nimmt. Zugegebenermaßen eine ziemlich blöde Idee, aber möglich, dass jemand sowas macht.



  • 314159265358979 schrieb:

    Was ist, wenn der operator << überladen ist und z.B. einen T&& nimmt. Zugegebenermaßen eine ziemlich blöde Idee, aber möglich, dass jemand sowas macht.

    Wenn du so blöde Ideen unterstützen willst (Man kann ja nie wissen, ob das in irgendeinem abgefahrenen Entwicklerhirn nicht Sinn macht), kommst du ums forwarding nicht herum. Da DU das aber wohl nicht machen wirst, würd mich an deiner Stelle aber lieber vorher um die anderen issues kümmern, bevor du das sojemand abgefahrernen zur benutzung überlässt 😉


  • Mod

    Es geht auch ohne Rekursion (womit sich die Forwarding-frage einigermaßen erledigt).

    void printf_(const char*& format)
    {
        while ( *format && *format != '%' )
            std::cout << *format++;
    }
    
    template <typename... T>
    void printf(const char* format, T&&... args)
    {
        bool foo[] = { ( printf_( format ),
                       *format == '%' ? ( ++format, std::cout << std::forward<T>( args ) ) : false )..., 
                        std::cout << format };
    }
    

    Intressanter wäre noch ein printf, das den Formatstring als Templateargument bekommt; dann könnte man die Analyse bereits beim Compilieren durchführen und entsprechende Fehler bezüglich der Parameterzahl erkennen.



  • Ist das denn so erlaubt? Du veränderst format hier ja 2 mal in einem Ausdruck.


  • Mod

    314159265358979 schrieb:

    Ist das denn so erlaubt? Du veränderst format hier ja 2 mal in einem Ausdruck.

    Das ist kein Problem. Entscheidend ist schließlich nicht, ob mehrere Veränderungen Teil eines Ausdruckes sind, sondern ob zwischen diesen Veränderungen eine entsprechende Reihenfolge (sequenced before (1.9), bzw. dazwischenliegende Sequenzpunkte in C++03-Terminolofgie) vorliegt. Das ist hier der Fall (in C++03, jeder Initialiserier ist ein vollständiger Ausdruck und zwischen vollständigen Ausdrücken liegt immer ein Sequenzpunkt; für C++0x wird das noch einmal explizit in 8.5.4/4 geregelt (nicht in n3242, dort gibt es nur 8.5.1/17, der in 3290 überflüssig wäre und deshalb dort gestrichen wurde)) ). und innerhalb jedes Initialisierers ist die korrekte Sequenzierung durch die verwendeten Operatoren garantiert.


Log in to reply