Klassen mit constexpr Konstruktor als static reference member.



  • Hallo,

    leider ist mir keine bessere Überschrift eingefallen aber was ich haben will:

    Eine Klasse als Parameterklasse: In dieser werden Werte aus einem Datenblatt gespeichert. Also zur Kompilierzeit schon bekannt => Konstruktor als constexpr.

    Eine Klasse als Konfiguration: In dieser werden Referenzen zu den Parameterobjekten gespeichert. Diese sind zur Kompilierzeit auch schon bekannt => zur Optimierung ebenfalls den Konstruktor als constexpr.

    Da es mehrere Konfigurationen gibt soll in der Klasse Konfiguration die aktuell ausgewählte Konfiguration gespeichert werden.

    Da es sich um statische/bekannte Werte handelt wäre es super wenn der Compiler so viel wie möglich Wegoptimieren könnte. Deshalb auch die Verwendung von constexpr.

    Hier mein Code der beim Compilieren Fehler erzeugt und ich weiß einfach nicht wie ich diesen ändern kann. 😕

    #include<iostream>
    
    class Parameter {
    public:
    	const unsigned int val;
    public:
    	constexpr Parameter(const decltype(val) val ) : val(val) {}
    };
    
    namespace parameters {
    static constexpr Parameter PARAM1{1};
    static constexpr Parameter PARAM2{2};
    static constexpr Parameter PARAM3{3};
    }
    
    class Configuration { //non-static reference member 'const Parameter& Configuration::param2', can't use default assignment operator
    private:
    	static Configuration &currentConfig;
    
    public:
    	const Parameter &param1;
    	const Parameter &param2;
    
    public:
    	constexpr Configuration(const Parameter &param1, const Parameter &param2) : param1{param1}, param2{param2}{}
    
    	static Configuration &getCurrent() {
    		return currentConfig;
    	}
    
    	static void setCurrent(const Configuration &config) {
    		currentConfig = config; //use of deleted function 'Configuration& Configuration::operator=(const Configuration&)'
    	}
    };
    
    namespace configurations {
    static constexpr Configuration CONFIG1{parameters::PARAM1, parameters::PARAM2};
    static constexpr Configuration CONFIG2{parameters::PARAM1, parameters::PARAM3};
    }
    
    Configuration &Configuration::currentConfig{configurations::CONFIG1}; //binding 'const Configuration' to reference of type 'Configuration&' discards qualifiers
    
    int main() {
    	std::cout << "Param1: " << Configuration::getCurrent().param1 << "\tParam2: " << Configuration::getCurrent().param2 << std::endl; //cannot bind 'std::basic_ostream<char>' lvalue to 'std::basic_ostream<char>&&'
    	Configuration::setCurrent(configurations::CONFIG2);
    	std::cout << "Param1: " << Configuration::getCurrent().param1 << "\tParam2: " << Configuration::getCurrent().param2 << std::endl;
    }
    

    Hat jemand eine Idee wie ich das richtig/besser Lösen könnte?


  • Mod

    godi schrieb:

    Eine Klasse als Parameterklasse: In dieser werden Werte aus einem Datenblatt gespeichert. Also zur Kompilierzeit schon bekannt => Konstruktor als constexpr.

    ok. Allerdings

    class Parameter {
    public:
    	const unsigned int val;
    public:
    	constexpr Parameter(const decltype(val) val ) : val(val) {}
    };
    

    hat das const bei der Definition von val nichts verloren. const ist für nicht-statische Member fast immer sinnlos.

    Eine Klasse als Konfiguration: In dieser werden Referenzen zu den Parameterobjekten gespeichert.

    Wieso Referenzen? Warum nicht einfach normale Objekte die gegebenenfalls Kopien von Prototypen darstellen?



  • Danke für deine Antwort.

    camper schrieb:

    ok. Allerdings hat das const bei der Definition von val nichts verloren. const ist für nicht-statische Member fast immer sinnlos.

    Warum ist dies sinnlos?
    Ich dachte wenn dies const ist und der Konstruktor constexpr ist, dann wird die Klasse quasie vollkommen wegoptimiert und bei einem Zugriff auf myParamObject.val wird direkt der Wert eingesetzt.
    Ausserdem kann mMn in diesem Fall auf getter (obwohl ich sonst immer getter schreibe) verzichtet werden und der Klassenmember public sein. Mit const kann dieser dann von aussen nicht geändert werden.

    camper schrieb:

    Wieso Referenzen? Warum nicht einfach normale Objekte die gegebenenfalls Kopien von Prototypen darstellen?

    Ja das ist eine gute Frage. Ich würde eben gerne haben, dass die Werte beim Kompilieren schon, wenn möglich, berechnet werden.
    Z.B wenn in der Klasse Configuration es eine Methode gibt mit:

    unsigned int mult() {
       return param1.val * param2.val;
    }
    

    dann kann diese ja vollständig wegoptimiert werden und der Wert direkt beim aufrufer eingesetzt werden.
    Das ist eigentlich mein Ziel.



  • godi schrieb:

    Z.B wenn in der Klasse Configuration es eine Methode gibt mit:

    unsigned int mult() {
       return param1.val * param2.val;
    }
    

    dann kann diese ja vollständig wegoptimiert werden und der Wert direkt beim aufrufer eingesetzt werden.

    Wenn ich darüber nachdenke dann kann der Wert nicht direkt beim aufrufer eingesetzt werden, da ich ja eine setter Methode in der Configuration-Klasse habe. Aber der Wert kann vorab berechnet werden weil ich ja das Configurationsobjekt als static constexpr anlege.



  • godi schrieb:

    camper schrieb:

    Wieso Referenzen? Warum nicht einfach normale Objekte die gegebenenfalls Kopien von Prototypen darstellen?

    Ja das ist eine gute Frage. Ich würde eben gerne haben, dass die Werte beim Kompilieren schon, wenn möglich, berechnet werden.

    Habe mich selbst noch einmal gefragt warum Referenz/Pointer. Mein ursprünglicher Gedanke dahinter war eigentlich, dass ich bei mehreren Konfigurationen des öfteren auch die selben Parameter verwende. Also wie im Ursprünglichen Bsp verwende ich für Konfiguration 1 und 2 jeweils Param1. Deshalb die Idee mit der Referenz.


  • Mod

    Ich dachte wenn dies const ist und der Konstruktor constexpr ist, dann wird die Klasse quasie vollkommen wegoptimiert und bei einem Zugriff auf myParamObject.val wird direkt der Wert eingesetzt.

    Die wird auch so Ziel von Optimierungen sein.

    Ausserdem kann mMn in diesem Fall auf getter (obwohl ich sonst immer getter schreibe) verzichtet werden

    Das ist das einzige Argument, und es ist äußerst schwach. Denk doch mal an die Inflexibilität.


  • Mod

    godi schrieb:

    Danke für deine Antwort.

    camper schrieb:

    ok. Allerdings hat das const bei der Definition von val nichts verloren. const ist für nicht-statische Member fast immer sinnlos.

    Warum ist dies sinnlos?

    Welchen Sinn sollte es haben?
    Wenn ein Member const deklariert wird, bedeutet das, dass der betreffende Member selbst dann const ist, wenn das Klassenobjekt es nicht ist. Das hat u.a. Konsequenzen in Bezug auf Kopierbarkeit: ein solches Objekt kann zwar copy-initialisiert werden, eine Zuweisung ist aber nicht möglich.
    Der einzige Fall, in dem das typischerweise so modeliert werden soll, sind Proxy-Objekte: für diese sind Initialisierung und Zuweisung semantisch völlig verschiedene Dinge.
    Nebenbei bemerkt spricht diese Begründung in den meisten Fällen auch gegen Referenzmember.

    Hier hast du aber nur ein Objekt, dass einen ganz gewöhnlichen Wert speichert. Wieso sollte dessen Kopiersemantik anders aussehen?

    camper schrieb:

    Ich dachte wenn dies const ist und der Konstruktor constexpr ist, dann wird die Klasse quasie vollkommen wegoptimiert und bei einem Zugriff auf myParamObject.val wird direkt der Wert eingesetzt.

    Ein constexpr-Konstruktor sorgt dafür dass die Initialisierung eines Klassenobjektes ein konstanter Ausdruck ist (sofern die die Parameter konstante Ausdrücke sind). Nicht mehr nicht weniger. Das hat nichts mit der Definition deines Klassenmembers zu tun.
    Falls nun das zu initialisierende Klassenobjekt const ist, ist es auch Member val ganz automatisch.

    camper schrieb:

    Ausserdem kann mMn in diesem Fall auf getter (obwohl ich sonst immer getter schreibe) verzichtet werden und der Klassenmember public sein. Mit const kann dieser dann von aussen nicht geändert werden.

    Das kann es sowieso nicht, wenn das Klassenobjekt const ist.

    godi schrieb:

    camper schrieb:

    Wieso Referenzen? Warum nicht einfach normale Objekte die gegebenenfalls Kopien von Prototypen darstellen?

    Ja das ist eine gute Frage. Ich würde eben gerne haben, dass die Werte beim Kompilieren schon, wenn möglich, berechnet werden.
    Z.B wenn in der Klasse Configuration es eine Methode gibt mit:

    unsigned int mult() {
       return param1.val * param2.val;
    }
    

    dann kann diese ja vollständig wegoptimiert werden und der Wert direkt beim aufrufer eingesetzt werden.
    Das ist eigentlich mein Ziel.

    Darüber solltest du noch einmal denken: ist ein Fall denkbar, in dem mit Kopien gearbeitet wird und das Ergebnis kein konstanter Ausdruck ist, bei Verwendung (äquivalenter) Referenzen aber schon?



  • Danke für eure ausführlichen Antworten! 🙂 👍

    Dadurch ist mir einiges klar geworden, besonders im bezug auf const.
    Ich hatte vorher schon herumprobiert mit Kopien jedoch kam eben der Fehler, dass der Kopierkonstruktor nicht existiert. Und ich bin nicht dahintergestiegen warum es da keinen gibt. Jetzt ist es klar!

    Also werde ich mein Programm in dieser Struktur machen:

    #include<iostream>
    
    class Parameter {
    public:
    	unsigned int val;
    public:
    	constexpr Parameter(const decltype(val) val ) : val(val) {}
    };
    
    namespace parameters {
    static constexpr Parameter PARAM1{1};
    static constexpr Parameter PARAM2{2};
    static constexpr Parameter PARAM3{3};
    }
    
    class Configuration {
    private:
    	static Configuration currentConfig;
    
    public:
    	Parameter param1;
    	Parameter param2;
    
    public:
    	constexpr Configuration(const Parameter param1, const Parameter param2) : param1{param1}, param2{param2}{}
    
    	static const Configuration getCurrent() {
    		return currentConfig;
    	}
    
    	static void setCurrent(Configuration config) {
    		currentConfig = config;
    	}
    
    	const unsigned int mult() const {
    		return param1.val * param2.val;
    	}
    };
    
    namespace configurations {
    static constexpr Configuration CONFIG1{parameters::PARAM1, parameters::PARAM2};
    static constexpr Configuration CONFIG2{parameters::PARAM1, parameters::PARAM3};
    }
    
    Configuration Configuration::currentConfig{configurations::CONFIG1};
    
    int main() {
    	//Configuration::getCurrent().param1.val = 10; //Doesn't work => This is fine!
    	std::cout << "Param1: " << Configuration::getCurrent().param1.val << "\tParam2: " << Configuration::getCurrent().param2.val << "\tMult: " << Configuration::getCurrent().mult() << std::endl;
    	Configuration::setCurrent(configurations::CONFIG2);
    	std::cout << "Param1: " << Configuration::getCurrent().param1.val << "\tParam2: " << Configuration::getCurrent().param2.val << "\tMult: " << Configuration::getCurrent().mult() << std::endl;
    }
    

Log in to reply