Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parameter pack to initialize member std::array

I have a class Color which is a thin wrapper around std::array. I would like to be able to use it in 2 ways:

  1. Color<5> color{1,2,3,4,5} should produce color.values = [1,2,3,4,5]
  2. Color<5> color{3} should produce color.values = [3,3,3,3,3]

What I currently have is:

template <size_t N>
class Color {
public:
    Color() = default;
    Color(std::array<float, N> values) : values{ values } {};

    template <IsNumeric... T>
    Color(T... v) : values{ static_cast<float>(v)... } { };

    ....

private:
    std::array<float, N> values;
}

This works correctly for my first-case. However for the second case it only produces: color.values = [3,0,0,0,0]. I am at a loss for how to get the second case to work. I have tried:

template <size_t N>
class Color {
public:
    Color() = default;
    Color(std::array<float, N> values) : values{ values } {};

    template <IsNumeric... T, typename std::enable_if<(sizeof...(T) == N), bool>::type = true>
    Color(T... v) : values{ static_cast<float>(v)... } { };

    template <IsNumeric T>
    Color(T v) : values{ std::array<float, N>{ static_cast<float>(v) } } { };

    ...
}

But this does not change anything.

like image 495
Chris Gnam Avatar asked Sep 04 '26 14:09

Chris Gnam


2 Answers

You can do this with the help of std::index_sequence and a helper function. Since you are already using concepts you should use a requires clause to replace std::enable_if.

Your code doesn't show what type the member values is, but the question title as well as the other constructor seems to imply that it is a std::array and that is what I use here. Anyway it should work as long as the type of values has a variadic constructor that does the correct thing.

namespace detail{
    template <std::size_t... Is, typename T>
    constexpr std::array<float, sizeof...(Is)> makeValues(std::index_sequence<Is...>, T v){
        return {((void)Is, v)...};
    }
}

template <size_t N>
class Color {
public:
    constexpr Color() = default;
    //constexpr Color(std::array<float, N> values) : values{ values } {};

    template <IsNumeric... T> requires (sizeof...(T) == N)
    constexpr Color(T... v) : values{ static_cast<float>(v)... } { };

    template <IsNumeric T>
    constexpr Color(T v) : values{detail::makeValues(std::make_index_sequence<N>{}, static_cast<float>(v))} { };

    std::array<float, N> values;
};

It can be verified by the following test main function:

int main(){
    static_assert(Color<2>{5,4}.values == std::array<float,2>{5,4});
    static_assert(Color<2>{5}.values == std::array<float,2>{5,5});
    // Color<3> doesntCompile{1,2};
}

Demo: https://godbolt.org/z/KGeehW5x6

like image 137
Weijun Zhou Avatar answered Sep 06 '26 05:09

Weijun Zhou


For types which need "index_sequence" in their interface, I tend to put it as template parameter:

template <typename IntSeq> class ColorImpl;

// Helper for variadic expansion
template <std::size_t, typename T> using always_t = T;

template <size_t... Is>
class ColorImpl<std::index_sequence<Is...>>
{
    std::array<float, sizeof...(Is)> values{};
public:
    constexpr ColorImpl() = default;
    constexpr explicit ColorImpl(std::array<float, sizeof...(Is)> values) : values{ values }
    {}

    // requires to avoid conflicts with default constructor and "fill" constructor
    constexpr ColorImpl(always_t<Is, float>... fs) requires (sizeof...(Is) > 1) :
        values{ fs... }
    {}

    constexpr explicit ColorImpl(float f) : values{ (static_cast<void>(Is), f)... } {}

    // ...
};

template <std::size_t N>
using Color = ColorImpl<std::make_index_sequence<N>>;

Demo

like image 21
Jarod42 Avatar answered Sep 06 '26 05:09

Jarod42



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!