Anzahl Elemente in initializer_list zum Übersetzungszeitpuntk begrenzen



  • Das sind wieder so Dinge, weshalb ich modernes C++ so gar nicht toll finde. Das Auskommentierte funktioniert natürlich wieder einmal nicht, und das andere funktioniert. Kennt jemand einen Weg das zu bewerkstelligen, ohne eine andere Signatur des Konstruktors zu bekommen?

    #include <initializer_list>
      
    class Foo {
        int a, b;
    public:
        Foo (const std::initializer_list<const int>& list) {
            //static_assert (2 == list.size());
        }
    };
    
    int main () {
        static constexpr std::initializer_list<const int> list = {1, 2};
    
        static_assert (2 == list.size());
    
        Foo f = list;
    }
    


  • Der Funktionsparameter const std::initializer_list<const int>& list ist keine Compile-Time Konstante und daher kann darauf kein static_assert angewendet werden (auch wenn size() seit C++14 als constexpr deklariert ist).
    Es wird ja nicht für jeden Aufruf static_assert angewendet, sondern einmalig beim Kompilieren der Funktion (auch wenn diese nirgendwo aufgerufen werden würde).

    s.a. static_assert on initializer_list::size()


  • Mod

    Typisches XY Problem. Davon abgesehen basiert dein Code auf einem Denkfehler. Funktionsparameter können niemals als constant expressions behandelt werden, weil sie über mehrere Aufrufe eben nicht konstant sind. Wenn ich

        constexpr Foo (const std::initializer_list<const int>& list) {
            MyTemplate<list.size()> t;
        }
    

    schreiben könnte, wäre die Typisierung dieser Funktion nicht durchführbar. Es spricht im Uebrigen nichts dagegen, einfach eine Exception zu werfen. Der Konstruktor kann ja auch als consteval definiert werden, dann wird die exception in jedem Fall zu einem Uebersetzungsfehler fuehren.

    class Foo {
    public:
        consteval Foo (const std::initializer_list<int>& list) {
            if (list.size() != 3) throw std::logic_error("");
        }
    };
    
    int main () {
        static constexpr std::initializer_list<int> list = {1, 2};
        Foo f(list);
    }
    

    https://coliru.stacked-crooked.com/a/612e4b1772124444

    PS: initializer_list<const int> ist auch 🤦♂

    PPS: Das was im OP versucht wird kann auch mit

        template <size_t N>
        Foo (const int (&arr)[N]) {
            static_assert (2 == N);
        }
    

    erreicht werden, aber das wird ein unnötig verkorkstes Interface ergeben.



  • @Columbo sagte in Anzahl Elemente in initializer_list zum Übersetzungszeitpuntk begrenzen:

    Typisches XY Problem. Davon abgesehen basiert dein Code auf einem Denkfehler. Funktionsparameter können niemals als constant expressions behandelt werden, weil sie über mehrere Aufrufe eben nicht konstant sind.

    Nein, ein Denkfehler ist es nicht. Eher war ich zu optimistisch, dass es funktionieren könnte. Nur schlägt hier wieder Type Decay zu, in dem aus einem static constexpr Parameter einfach ein const Parameter gemacht wird. Das sind so Dinge an modernen C++ die mich am Verstand der Entwickler zweifeln lässt. Es gibt nun consteval Funktionen, aber Parameter für solche Funktionen dürfen nicht constexpr sein?

    Wenn ich

        constexpr Foo (const std::initializer_list<const int>& list) {
            MyTemplate<list.size()> t;
        }
    

    schreiben könnte, wäre die Typisierung dieser Funktion nicht durchführbar.

    Natürlich wäre sie das, und zwar immer dann wenn das übergebene Objekt constexpr bzw. list.size() consteval wäre. Dann wäre nämlich zum Übersetzungszeitpunkt list.size() auswertbar, und würde eine entsprechende Non Type Template Parameter Spezialisierung liefern. Der Type Decay macht aber einen Strich durch die Rechnung. Nur das ist halt unsinnig und unlogisch, aber so vieles ist bei modernem C++ nicht logisch.

    Wenn man die initializer_list als static constexpr definiert, kann man dann MyTemplate<list.size()> t; im normalen Code verwenden.

    Es spricht im Uebrigen nichts dagegen, einfach eine Exception zu werfen. Der Konstruktor kann ja auch als consteval definiert werden, dann wird die exception in jedem Fall zu einem Uebersetzungsfehler fuehren.

    class Foo {
    public:
        consteval Foo (const std::initializer_list<int>& list) {
            if (list.size() != 3) throw std::logic_error("");
        }
    };
    
    int main () {
        static constexpr std::initializer_list<int> list = {1, 2};
        Foo f(list);
    }
    

    Wunderbar danach hatte ich gesucht, dieser Aspekt war mir noch nicht bewusst. Danke. Nur spuckt der g++ 11.2.0 wieder einmal vollkommen unsinnig Fehlermeldungen aus, wenn man den Konstruktor falsch nutzt. 😞

    https://coliru.stacked-crooked.com/a/612e4b1772124444

    PS: initializer_list<const int> ist auch 🤦♂

    PPS: Das was im OP versucht wird kann auch mit

        template <size_t N>
        Foo (const int (&arr)[N]) {
            static_assert (2 == N);
        }
    

    erreicht werden, aber das wird ein unnötig verkorkstes Interface ergeben.

    Darüber war ich auch schon gestolpert, aber das bedingt wieder andere Probleme.


  • Mod

    @john-0 sagte in Anzahl Elemente in initializer_list zum Übersetzungszeitpuntk begrenzen:

    Natürlich wäre sie das, und zwar immer dann wenn das übergebene Objekt constexpr bzw. list.size() consteval wäre. Dann wäre nämlich zum Übersetzungszeitpunkt list.size() auswertbar, und würde eine entsprechende Non Type Template Parameter Spezialisierung liefern.

    Ja, und die Funktionsdefinition muesste dann jedes mal neu kompiliert werden, was daraus effektiv ein Template machen wuerde. Nur leider hast Du es nicht entsprechend deklariert.

    Nein, ein Denkfehler ist es nicht. Eher war ich zu optimistisch, dass es funktionieren könnte.

    Du bist 'optimistisch', dass eine systemische Limitierung der Sprache ploetzlich ausgesetzt wird? Ist es auch Optimismus, wenn ein geisteskranker vom Dach springt, weil er fliegen wollte? Oder hat er einfach Gravitation nicht verstanden?

    PS: Es ist i.A. kein gutes Zeichen, seine eigene Ignoranz auf Fehldesign des betreffenden Systems zu schieben..



  • @Columbo sagte in Anzahl Elemente in initializer_list zum Übersetzungszeitpuntk begrenzen:

    Ja, und die Funktionsdefinition muesste dann jedes mal neu kompiliert werden, was daraus effektiv ein Template machen wuerde. Nur leider hast Du es nicht entsprechend deklariert.

    Hier denkt jemand nicht mit! Das Problem existiert doch exakt mit den throw std::logic_error in der consteval Funktion ebenfalls. Das lässt sich nur dann zum Compilezeitpunkt prüfen, wenn die Definition vorliegt und geprüft wird, und natürlich kann man keine consteval Funktion nutzen, ohne dass der Definition sichtbar ist. Der Compiler mahnt das sogleich an. Also, wo bitte ist das Problem constexpr Ausdrücke als Parameter zu erlauben?

    PS: Es ist i.A. kein gutes Zeichen, seine eigene Ignoranz auf Fehldesign des betreffenden Systems zu schieben..

    Das schreibt der richtige. Du übersiehst die Problematik ebenfalls nicht vollständig, und haust hier trotzdem maximal überhebliche Kommentare raus. Etwas mehr Demut täte Dir gut.


  • Mod

    Das Problem existiert doch exakt mit den throw std::logic_error in der consteval Funktion ebenfalls. Das lässt sich nur dann zum Compilezeitpunkt prüfen, wenn die Definition vorliegt

    Verstehste den Unterschied zwischen 'pruefen' und 'uebersetzen'?
    🙄



  • @Columbo sagte in Anzahl Elemente in initializer_list zum Übersetzungszeitpuntk begrenzen:

    Verstehste den Unterschied zwischen 'pruefen' und 'uebersetzen'?
    🙄

    Mit dem Übersetzen ist es nicht getan. Die Funktionen müssen zum Übersetzungszeitpunkt ausgeführt werden, nur so weiß der Compiler, dass die Bedingungen erfüllt bzw. nicht erfüllt sind. Würde nur übersetzt, dann kann der Compiler die Ausdrücke nicht auswerten und prüfen, da die Parameter ja nicht als Übersetzungszeitpunkt konstant definierbar sind bzw. er müsste eigentlich auch die Variante mit throw verwerfen. Tut er aber nicht.


Log in to reply