Variadic template und void?



  • Hallo,

    ich habe ein Problem mit einem variadischen template, und zwar

    #include <iostream>
    using namespace std;
    
    template<typename ...Args>
    struct Foo {
    	void bar(Args...) {
    		cout << "Foo::bar\n";
    	}
    };
    
    int main() {
    	Foo<int> f1; // Geht
    	f1.bar(1);
    
    	Foo<void> f2; // Fehler
    	f2.bar();
    }
    

    Fehlermeldung ist "error: invalid parameter type 'void'".

    Wie kann ich das variadic template dazu bringen ein void zu akzeptieren? Und warum geht das eigentlich nicht, der Parameter wird doch nirgends verwendet, also sollte void doch zulässig sein oder nicht?


  • Mod

    happystudent schrieb:

    Und warum geht das eigentlich nicht, der Parameter wird doch nirgends verwendet, also sollte void doch zulässig sein oder nicht?

    Du darfst eben nur richtige Typen in Funktionssignaturen nutzen, auch wenn du sie selber im Code nicht benutzt. Wie soll der Compiler Code erzeugen, der ein void-Objekt an eine Funktion übergibt, wenn void doch der nicht-Objekt-Typ ist?

    Wie kann ich das variadic template dazu bringen ein void zu akzeptieren?

    Kommt drauf an. Was willst du überhaupt erreichen?

    Vielleicht hilft dir dieser Link, hinter dem ein ähnliches Problem behandelt wird:
    http://stackoverflow.com/questions/13372173/using-void-template-arguments-in-c



  • Der Aufruf f2.bar() deutet darauf hin, dass Du eine Instanz mit null Parametern haben möchtest. Wieso solltest Du dann eine Instanz mit einem Parameter (void) erzeugen? Erzeuge doch dann auch eine Instanz mit null Parametern 😉



  • SeppJ schrieb:

    Du darfst eben nur richtige Typen in Funktionssignaturen nutzen, auch wenn du sie selber im Code nicht benutzt. Wie soll der Compiler Code erzeugen, der ein void-Objekt an eine Funktion übergibt, wenn void doch der nicht-Objekt-Typ ist?

    Ja, aber foo() ist doch das selbe wie foo(void), da wird ja auch kein Objekt übergeben?

    SeppJ schrieb:

    Kommt drauf an. Was willst du überhaupt erreichen?

    Ich will ein Klassen-template, welches eine Funktion enthält die genau die gleiche Signatur hat wie die template Parameter der Klasse. Dabei soll void explizit auch zugelassen sein.

    LordJaxom schrieb:

    Der Aufruf f2.bar() deutet darauf hin, dass Du eine Instanz mit null Parametern haben möchtest. Wieso solltest Du dann eine Instanz mit einem Parameter (void) erzeugen? Erzeuge doch dann auch eine Instanz mit null Parametern

    Du meinst Foo<> f; ?

    Das will ich nicht, denn dann kann man keinen default Parameter mehr angeben:

    #include <iostream>
    using namespace std;
    
    template <typename T = double>
    struct Foo
    {
    	void bar(T)
    	{
    
    	}
    };
    
    int main() {
        Foo<> f;
        f.bar();
    }
    

    Jetzt ist Foo<> f; gleich Foo<double> f; somit habe ich keine Möglichkeit mehr eine template Instanz mit void zu erzeugen, da ambigious mit dem default-Parameter 😞


  • Mod

    wie wäre es mit

    template<typename T = double, typename... Args>
    struct Foo {
        struct null_type {};
        void bar(typename std::conditional<std::is_same<T, void>{}, null_type, T>::type = null_type{}, Args...) {
            cout << "Foo::bar\n";
        }
    };
    

  • Mod

    happystudent schrieb:

    SeppJ schrieb:

    Du darfst eben nur richtige Typen in Funktionssignaturen nutzen, auch wenn du sie selber im Code nicht benutzt. Wie soll der Compiler Code erzeugen, der ein void-Objekt an eine Funktion übergibt, wenn void doch der nicht-Objekt-Typ ist?

    Ja, aber foo() ist doch das selbe wie foo(void), da wird ja auch kein Objekt übergeben?

    Ein Template ist kein Makro. Das ist keine einfache Textersetzung. Es mag zwar in C++ aus Gründen der Kompatibilität gültig sein, void foo(void); zu schreiben und das heißt tatsächlich, dass foo keine Parameter nimmt, aber template <typename T> void foo(T); heißt eben, dass foo ein Objekt vom Typ T erwartet. Und ein Typ kann nicht void sein. Die syntaktische Bedeutung von Templatecode darf sich nämlich nicht ändern in Abhängigkeit von den benutzten Typen. Sonst könnte man leicht Beispiele konstruieren, die zu dem verrücktesten Verhalten führen. Das ist auch der Grund, wieso man bei Dingen, die vom Templateparameter abhängen, immer dazu schreiben muss, wenn es sich dabei um einen Typnamen oder ein Template handeln muss.



  • SeppJ schrieb:

    Das ist auch der Grund, wieso man bei Dingen, die vom Templateparameter abhängen, immer dazu schreiben muss, wenn es sich dabei um einen Typnamen oder ein Template handeln muss.

    Nein, nicht immer.

    template <typename T> struct pathological : example<T>::type {};
    

    Hier wäre typename nicht erlaubt.



  • camper schrieb:

    wie wäre es mit

    Nett, das scheint zu funktionieren. Werd ich wohl nehmen (auch wenns den calltip häßlich macht) 👍

    SeppJ schrieb:

    Ein Template ist kein Makro. Das ist keine einfache Textersetzung. Es mag zwar in C++ aus Gründen der Kompatibilität gültig sein, void foo(void); zu schreiben und das heißt tatsächlich, dass foo keine Parameter nimmt, aber template <typename T> void foo(T); heißt eben, dass foo ein Objekt vom Typ T erwartet. Und ein Typ kann nicht void sein. Die syntaktische Bedeutung von Templatecode darf sich nämlich nicht ändern in Abhängigkeit von den benutzten Typen. Sonst könnte man leicht Beispiele konstruieren, die zu dem verrücktesten Verhalten führen. Das ist auch der Grund, wieso man bei Dingen, die vom Templateparameter abhängen, immer dazu schreiben muss, wenn es sich dabei um einen Typnamen oder ein Template handeln muss.

    Schon, aber ich finds ziemlich blöd dass die Syntax hier nicht wirklich einheitlich ist. Wenn ich

    template<typename ...Args>
    struct Foo 
    {
    	void bar(Args...) 
    	{
    
    	}
    };
    
    int main()
    {
    	Foo<> f;
    	f.bar();
    }
    

    schreibe, geht das ja auch. Ist ja quasi genau das selbe wie wenn man void schreiben könnte. Nur dass ich die Klammern halt leer lasse anstatt explizit void zu schreiben, was aber wenigstens eindeutig wäre, denn so sieht es erstmal aus wie ein default-Wert und wird noch dazu mehrdeutig...


  • Mod

    Das ist eben der Unterschied, ob man Nichts auf ein Blatt Papier schreibt oder ob man "Nichts" auf ein Blatt Papier schreibt. Das Blatt Papier sieht hinterher sehr unterschiedlich aus.



  • Und in C sind void f() und void f(void) tatsächlich je nach Kontext unterschiedliche Dinge (in einer Deklaration deklariert ersteres eine Funktion mit unbekannter Parameterliste und letzteres eine Funktion ohne Parameter, während in einer Definition beide eine Funktion ohne Parameter definieren). In C++ gibt es dagegen lediglich aus Kompatibilitätsgründen die Zusatzregelung, dass letzteres generell gleichwertig zu ersterem ist, was zwar nicht ganz das selbe ist wie in C, aber zumindest nicht der stärkeren Typisierung in die Quere kommt und dabei hilft, dass große Mengen an "gutem C" auch in C++ kompilieren. Ich denke, es sollte recht offensichtlich sein, dass der bla(void) Kram als C-Altlast anzusehen ist und in C++ Code nicht verwendet werden sollte... 😉


Anmelden zum Antworten