0
\$\begingroup\$

I am writing color model logic in C++. I ask review in defining limits for the color model, by saying Limits I mean this. Consider a struct which represents RGB color model.

template<typename T>
struct RGB {
   T r,g,b;
};

So I declared a type RGB<T> where T can be any arithmetic type (the assertion is done, don't worry). So now I can create types using RGB_Float = RGB<flaot> or using RGB_888 = RGB<unsigned char> and so on. As you already guessed I am doing image processing and sometimes there comes a time where I want a type RGB<float> where each component limits are [0-1] and sometimes I want same RGB<float> with the limits [10-20] (An example) . Also I need each type to know its limits, at compile time. Syntax is like RGB_Float::Limits::max() \\Returns max RGB_Float and RGB_Float::Limits::min() \\ Returns min RGB_Float. So I need to change my struct RGB<T> this way.

template<typename T, typename LIMITS>
struct RGB {
    using Limits = LIMITS;
    T r,g,b;

}

And before declaring any RGB<T,LIMITS> type I need to create a struct like

RGB_FloatLimits {
     static RGB<float,RGB_FloatLimits> max( return {1.0f,1.0f,1.0f};
     ... // min implementation too.
}

And then using RGB_Float = RGB<float,RGB_FloatLimits>;

I know that this is a bad solution but I think it's the only if you use C++17. I spent a week trying to pass the limits as a template argument to a color model. Please review this solution and I am 90% sure that there is a better way to do this. Thx in advance.

Note In any implementation of the limits sizeof the color model must remain the same.(So we must store the max and min statically)

\$\endgroup\$
1
  • 1
    \$\begingroup\$ There is not enough code in the question to provide a good review. The question borders on hypothetical code. \$\endgroup\$
    – pacmaninbw
    Dec 20 '20 at 19:31
-1
\$\begingroup\$

C++11

Pre C++20 you have to use the type traits based approach, which is essentially what you have done. I dont think this is a bad solution, but probably the way to go if you need to.

C++20 solution (if integral min and max are ok also for C++11 )

In C++ 20 this all becomes easier as it finally allows for class and float non-type template arguments.

template<typename T, T min, T max>
struct RGB {
    T r,g,b; 
}

works for T = int, T = char, T = float and so on.

This is the most straightforward solution I could think of that does not need any helper classes. If you only need min and max this is probably good.

If you could live with integer min and max then this also works pre C++20. Simply use int for the min and max template arguments.

The drawback is that in every function that accepts an RGB you need three template parameters (T, min and max), which could create a lot of noise in the code and make it less readable.

In case you need more than two config parameters

In case min and max is not a sufficient description of the number range, you could add addtional template parameters. However I would not advise this but make the parameters now a class.

In C++20 this is very straightforward again, as you can also use class values as template parameters. This is a bit more clear than the previous type based traits approach (which is also the one you are using).

template <class T>
struct RGBconfig<T> {
   T min, max;
}

template<typename T, RGBconfig<T> config>
struct RGB {
    T r,g,b; 
}

use as

constexpr RGBconfig<float> float1020 = {10, 20}  

using RGBfloat1020 = RGB<float, float1020>  

alternatively one could do

using RGBfloat1020 = RGB<float, RGBconfig<float> {10,20} >  

Reduce to one template parameter => include type in RGBconfig

Perhaps it is even possible to reduce it down to one template argument

template <class T>
struct RGBconfig<T> {
   using type = T;
   T min, max;
}

template<auto config>
struct RGB {
    typename decltype(config)::type    r,g,b; 
}

then use as

constexpr RGBconfig<float> float1020 = {10, 20};
using RGBfloat1020 = RGB<float1020>;

or

using RGBfloat1020 = RGB<RGBconfig<float> {10,20} >;
\$\endgroup\$
-1
\$\begingroup\$

I would simply use a traits configuration but use a default (so it can be overridden).

namespace Color
{
    template<typename T>
    struct RGB_Traits;         // No definition by default

    template<typename T, typename ColourTraits = RGB_Traits<T>>
    struct RGB {
        using Traits = ColourTraits;
        T r,g,b;
    };


    template<>
    struct RGB_Traits<float>
    {
        static RGB<float>  max() {return {1.0, 1.0, 1.0};}
        static RGB<float>  min() {return {0.0, 0.0, 0.0};}
    };
    template<>
    struct RGB_Traits<unsigned char>
    {
        static RGB<unsigned char>  max() {return {255, 255, 255};}
        static RGB<unsigned char>  min() {return {0, 0, 0};}
    };

    using RGB_Float = RGB<float>;
    using RGB_888   = RGB<unsigned char>;
}

#include <iostream>
int main()
{
    Color::RGB_888   data;
    std::cout << static_cast<int>(decltype(data)::Traits::max().r) << "\n";
}
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.