3

I'm sorry for the long description, I tried to reduce the example as much as possible to illustrate my problem. I have the following code:

#include <concepts> #include <type_traits> template<typename T> concept NumericPropertyValue = (std::is_integral_v<T> && !std::is_same_v<T, bool>) || std::is_floating_point_v<T>; template<NumericPropertyValue T> class Property { public: Property(const char* name, T minValue, T maxValue, T defaultValue) : _name(name) , _minValue(minValue) , _maxValue(maxValue) , _value(defaultValue) { } T operator=(T value) { // check the value, ... return _value; } operator T() const { return _value; } // some other utility functions private: const char* const _name; const T _minValue; const T _maxValue; T _value; }; struct SomeType { SomeType() : p1("p1Name", -100, 100, 0) , p2("p2Name", 0.0, 1.0, 0.0) { } Property<int> p1; Property<double> p2; }; int main() { SomeType test1; SomeType test2; return 0; } 

As you can see all instances of SomeType have their Property members with the same parameters, meaning that more than one instance of:

const char* const _name; const T _minValue; const T _maxValue; 

is just wasting memory and they could be shared. One option to fix this problem is to have a PropertyMetadata class to store this info, something like:

template<NumericPropertyValue T> struct PropertyMetadata { const char* const _name; const T _minValue; const T _maxValue; }; template<NumericPropertyValue T> class Property { public: Property(const PropertyMetadata<T>& metadata, T defaultValue) : _metadata(metadata) , _value(defaultValue) { } T operator=(T value) { // check the value, ... return _value; } operator T() const { return _value; } private: const PropertyMetadata<T>& _metadata; T _value; }; PropertyMetadata<int> p1Metadata("p1Name", -100, 100); PropertyMetadata<double> p2Metadata("p2Name", 0.0, 1.0); struct SomeType { SomeType() : p1(p1Metadata, 0) , p2(p2Metadata, 0.0) { } Property<int> p1; Property<double> p2; }; 

The second approach works and it doesn't waste space, but it's very inconvenient compared to the first approach.

Is it possible to generate objects with static storage directly in the constructor initializer list or any other way to share this metadata between all instances of SomeType without having to declare statics?

Note: In this simple example PropertyMetadata has only few parameters and it might not seem like a huge waste of memory, but in reality it has more members and some of them use dynamic allocation.

2 Answers 2

1

I'm acutally not sure if this is valid or undefined behaviour, but I post it here as an answer because it is to long for a comment.

You could have a static variable in an immediate invoked lambda, that returns a reference to that static local variable.

struct SomeType { SomeType() : p1([]() -> auto& { static auto pm = PropertyMetadata<int>{"p1Name", -100, 100}; return pm; }(), 0) , p2([]() -> auto& { static auto pm = PropertyMetadata<double>{"p2Name", 0.0, 1.0}; return pm; }(), 0.0) { } Property<int> p1; Property<double> p2; }; 

Here the full code:

#include <concepts> #include <type_traits> #include <iostream> template<typename T> concept NumericPropertyValue = (std::is_integral_v<T> && !std::is_same_v<T, bool>) || std::is_floating_point_v<T>; template<NumericPropertyValue T> struct PropertyMetadata { const char* const _name; const T _minValue; const T _maxValue; }; template<NumericPropertyValue T> class Property { public: Property(const PropertyMetadata<T>& metadata, T defaultValue) : _metadata(metadata) , _value(defaultValue) { } T operator=(T value) { // check the value, ... return _value; } operator T() const { return _value; } public: const PropertyMetadata<T>& _metadata; T _value; }; struct SomeType { SomeType() : p1([]() -> auto& { static auto pm = PropertyMetadata<int>{"p1Name", -100, 100}; return pm; }(), 0) , p2([]() -> auto& { static auto pm = PropertyMetadata<double>{"p2Name", 0.0, 1.0}; return pm; }(), 0.0) { } Property<int> p1; Property<double> p2; }; int main() { SomeType test1; SomeType test2; return 0; } 
Sign up to request clarification or add additional context in comments.

1 Comment

I think this is correct and I tried something similar based on stackoverflow.com/users/12024331/florian-thonig answer. It looks quite complicated to use, but I think it can be improved with some MACRO HORRORS
0

It is possible to create static variables in block scope. The C++ standard covers, that they are initialized ony once.

See more here: https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
and here: https://www.modernescpp.com/index.php/thread-safe-initialization-of-data#h3-static-variables-with-block-scope

The last link shows the concept of a singleton pattern, where a static variable is used like this:

class MySingleton{ public: static MySingleton& getInstance(){ static MySingleton instance; // volatile int dummy{}; return instance; } private: MySingleton()= default; ~MySingleton()= default; MySingleton(const MySingleton&)= delete; MySingleton& operator=(const MySingleton&)= delete; }; 

So you could initialize a static variable in a similar manner.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.