Search code examples
c++templatescolorsmetaprogrammingvariadic-templates

C++ struct field name and types from variadic template


Hey I am trying to implement Color space logic with c++. As you may see every color space has its unique color components with its unique ranges and names, so I tried to implement a space component logic first.

template<typename T, T min, T max>
struct SpaceComponent {
    static constexpr T MIN = min;
    static constexpr T MAX = max;
    T value;

    SpaceComponent() = default;

    SpaceComponent(T value) : value(static_cast<T>(value)) {
    }

    operator T() const {
        return value > MAX ? MAX : value < MIN ? MIN : value;
    }

    operator SpaceComponent<T, MIN, MAX>() const {
        return SpaceComponent<T, MIN, MAX>(static_cast<T>(value));
    }

    inline bool operator==(const SpaceComponent &other) const {
        return value == other.value;
    }

    inline bool operator!=(const SpaceComponent &other) const {
        return !(*this == other);
    }

    inline bool operator>(const SpaceComponent &other) const {
        return value > other.value;
    }

    inline bool operator<(const SpaceComponent &other) const {
        return !(*this > other) && *this != other;
    }

    inline bool operator>=(const SpaceComponent &other) const {
        return !(*this < other);
    }

    inline bool operator<=(const SpaceComponent &other) const {
        return !(*this > other);
    }

    inline SpaceComponent &operator=(const T &elem) {
        if (value == elem) {
            return *this;
        }
        value = static_cast<T>(elem);
        return *this;
    }

    inline SpaceComponent &operator=(const SpaceComponent &other) {
        if (*this == other) {
            return *this;
        }
        *this = other.value;
        return *this;
    }

    inline SpaceComponent &operator++() {
        *this = static_cast<T>(++value);
        return *this;
    }

    inline SpaceComponent &operator--() {
        *this = static_cast<T>(--value);
        return *this;
    }

    inline SpaceComponent operator+(const T &elem) const {
        SpaceComponent result;
        result = static_cast<T>(value + elem);
        return result;
    }

    inline SpaceComponent operator-(const T &elem) const {
        SpaceComponent result;
        result = static_cast<T>(value - elem);
        return result;
    }

    inline SpaceComponent operator*(const T &elem) const {
        SpaceComponent result;
        result = static_cast<T>(value * elem);
        return result;
    }

    inline SpaceComponent operator/(const T &elem) const {
        SpaceComponent result;
        result = static_cast<T>(value / elem);
        return result;
    }

    inline SpaceComponent operator+(const SpaceComponent &other) const {
        SpaceComponent result;
        result = static_cast<T>(value + other.value);
        return result;
    }

    inline SpaceComponent operator-(const SpaceComponent &other) const {
        SpaceComponent result;
        result = static_cast<T>(value - other.value);
        return result;
    }

    inline SpaceComponent operator*(const SpaceComponent &other) const {
        SpaceComponent result;
        result = static_cast<T>(value * other.value);
        return result;
    }

    inline SpaceComponent operator/(const SpaceComponent &other) const {
        SpaceComponent result;
        result = static_cast<T>(value / other.value);
        return result;
    }

    inline SpaceComponent operator+=(const T &elem) {
        *this = *this + elem;
        return *this;
    }

    inline SpaceComponent operator-=(const T &elem) {
        *this = *this - elem;
        return *this;
    }

    inline SpaceComponent operator*=(const T &elem) {
        *this = *this * elem;
        return *this;
    }

    inline SpaceComponent operator/=(const T &elem) {
        *this = *this / elem;
        return *this;
    }

    inline SpaceComponent &operator+=(const SpaceComponent &other) {
        *this = *this + other;
        return *this;
    }

    inline SpaceComponent &operator-=(const SpaceComponent &other) {
        *this = *this - other;
        return *this;
    }

    inline SpaceComponent &operator*=(const SpaceComponent &other) {
        *this = *this * other;
        return *this;
    }

    inline SpaceComponent &operator/=(const SpaceComponent &other) {
        *this = *this / other;
        return *this;
    }


};

By this logic I can create a color component of any type I want and it will not exit it's ranges(see implementation). Note that I am keeping MIN and MAX statically as I do not want my space component to increase in size(imagine what will be the size of 4096x4096 image in my ram if I do so).

Then I tried to implement different spaces of colors

struct RGB {
    SpaceComponent<unsigned char, 0, 255> r;
    SpaceComponent<unsigned char, 0, 255> g;
    SpaceComponent<unsigned char, 0, 255> b;

    inline RGB operator+(const RGB &other) {
        RGB rgb;
        rgb.r = r + other.r;
        rgb.g = g + other.g;
        rgb.b = b + other.b;
        return rgb;
    }
};

struct ARGB {
    SpaceComponent<unsigned char, 0, 255> a;
    SpaceComponent<unsigned char, 0, 255> r;
    SpaceComponent<unsigned char, 0, 255> g;
    SpaceComponent<unsigned char, 0, 255> b;

    inline ARGB operator+(const ARGB &other) {
        ARGB argb;
        argb.a = a + other.a;
        argb.r = r + other.r;
        argb.g = g + other.g;
        argb.b = b + other.b;
        return argb;
    }
};

I stopped at this two as I realized that I need to write all operator overloading logic for all the spaces and that is huge work. I need somehow implement the operator overloading in one struct struct Space and derive all others from that. Note I cannot have any virtual methods as any pointer to vtable will increase the sizeof(Space) and it will raise problems that I already mentioned.I think I need something like template meta-programming to do this (using macros is my last choice) or using some technique like CRTP, as a draft I think my Space implementation can look like this.

using RGB = Space<SpaceComponent<unsigned char,0,255> r,SpaceComponent<unsigned char,0,255> g,SpaceComponent<unsigned char,0,255> b>;

I know that it's illegal to write like this, but can I have a struct which syntax will at least be nearly similar to this? Thx in advance.


Solution

  • I think it is possible if all components have the same type. We can declare Space like this:

    template <typename T, typename ...Components>
    struct Space {...}
    

    and use syntax like:

    Space<unsigned char, Component<'r'>, Component<'g'>, Component<'b'>>;
    

    where

    template <char Id>
    struct Component{...} // Component is just wrapper for ComponentImpl (for creation, to not duplicate types every time)
    
    template <typename T> // your `SpaceComponent`
    struct ComponentImpl{...} 
    

    Then inside Space we can use constexpr function to fill std::array<ComponentImpl>. And inside operator+ just iterate through this array and sum all components.

    Also we (probably) does not have to store T min, T max, we can use std::numeric_limits to get them.

    UPDATE:
    I wrote some prototype of this, take a look: https://godbolt.org/z/oEThae