Pass data via template argument, C++

108 Views Asked by At

I am thinking about having color spaces structs in my program. Now as you can see each color space has its limitations for each of its component. So consider a struct like this.

template<typename T>
struct SomeColorSpace{
    T component1,component2,.....; // or std::array<T,N> components, N is compile-time const
};

Now I need somehow define a range so I create a struct.

template<typename T>
struct Range{

    T min, max;

    // Some constructors initializing min and max...
};

Now I need my color space struct to know what is the range of each of its component. But as you see I can't just hold std::array<Ranges<T>,N> ranges in my struct as the ranges differ from type to type. As an example consider struct RGB each of the component may be float or unsigned char so when the components are float the ranges of each component becomes [0,1]; and when it is unsigned char it is [0,255];. Writing template specialization for each of these case is a solution but instead I want a signature like this. using RGBFloat = RGB<float,Range<float>...>. I mean I want to pass the ranges via template parameter pack. I know that for instance Range<float>(0,1); is a non-type template parameter so one workaround is to refactor struct Range like this

template<typename T,T _MIN, T _MAX>
struct Range{
    static constexpr T MIN = _MIN;
    static constexpr T MAX = _MAX;
};

So I can pass Range<T,T min, T max> as template parameter pack, but then I have to do keep the pack in a std::tuple(I do not want this either). My question is do you see any other possibility via refactoring the Range struct or something else to have this signature of defining a color space. using DefineColorSpace = ColorSpace<unsigned char, ranges....>. I know that C++ 20 has huge refactors for non-type template parameters , but I am using clang and it seem that they do not support that feature yet. Any advice can be helpful, thanks)

2

There are 2 best solutions below

0
On BEST ANSWER

Support for literal classes as non-type template parameter is already in clang trunk. See it live on godbolt:

template<typename T>
struct Range
{
    T min, max;
};

template<typename T, Range<T> R>
struct SomeColorSpace
{
    static constexpr Range<T> r = R;

    T component1, component2;
};

using Rgb = SomeColorSpace<float, Range<float>{0.0f, 1.0f}>;

auto test()
{
    Rgb color{0.4f, 0.1f};

    float c1 = color.component1;
    float min = Rgb::r.min;

    return Rgb::r.max;
}

So it will arrive soon in clang. Until then a possible workaround is to use traits. See it live on godbolt:

template<class T, class Trait>
struct SomeColorSpace
{
    static constexpr T min = Trait::min;
    static constexpr T max = Trait::max;

    T component1, component2;
};

struct RgbTrait
{
    static constexpr float min = 0.0f;
    static constexpr float max = 1.0f;
};

using Rgb = SomeColorSpace<float, RgbTrait>;

auto test()
{
    Rgb color{0.4f, 0.1f};

    float c1 = color.component1;
    float min = Rgb::min;

    return Rgb::max;
}
0
On

Not really clear what you want, as for color, r/g/b/(a) would have same range depending on type.

So a simple traits allows to map minimum/maximum of float to [0.f,1.f] and std::uint8_t to [0,255]:

template <typename T> struct RangeTraits;

template <> struct RangeTraits<double>
{
    constexpr double min = 0.;
    constexpr double max = 1.;
};
template <> struct RangeTraits<std::uint8_t>
{
    constexpr std::uint8_t min = 0;
    constexpr std::uint8_t max = 255;
};
// ...

And

template <typename T>
struct SomeColorSpace{
    std::array<T, N> components;// N is compile-time constant

// use RangeTraits<T>::min, RangeTraits<T>::max when needed.
};

Another interpretation is that your template parameter are "just" default values for initialization:

template <typename T, T DefaultMin, T DefaultMax>
struct Range{
    T min = DefaultMin;
    T max = DefaultMax;

    // Some constructors initializing min and max...
};

but those default should be forget afterward

So you might be able to do something like:

std::array<Range<std::int8_t>, 2> components{Range<std::int8_t, 0, 127>, Range<std::int8_t, -10, 10>};

Then you might do:

template <typename T, T...> struct Range;

template <typename T>
struct Range<T>
{
    T min;
    T max;

    Range(T min, T max) : min(min), max(max) {}

    template <T Min, T Max,>
    Range(Range<T, Min, Max>& rhs) : min(rhs.min), max(rhs.max) {}

    Range(Range& rhs) = default;
    // ...
};

template <typename T, T Min, T Max>
struct Range<T, Min, Max>
{
    T min = Min;
    T max = Max;

    Range() = default;
    Range(Range& rhs) = default;

    // Possibly keep constructors of conversion and the one with runtime min/max
    // ...
};