I'm attempting to create a color class of variable size- given a template-determined array of values, I'd like to create named aliases of each value in the array, ie:
template<int C = 3, typename T = unsigned char>
class Color {
public:
  union {
    T v[C];
    struct {
      T r, g, b, a;
    };
  };
};
However, if I try to use the same class for C=3, the union mandates a size of 4 bytes (the 'a' member). Alternatively, using a mathematically expressed bitfield size for a (struct named a, anonymous T member, size evaluates to 1 at C>3), the compiler issues a permissive warning (non-suppressible, as per In gcc, how to mute the -fpermissive warning? ), something unsuitable for a larger-scale API.
How would I go about allowing a single class to handle different numbers of variables, while retaining per-variable names and without implementing recursive-include macro magic (tried this, shouldn't have). Thanks in advance!
Edit: To clarify the question, an answer to any of the following will solve this problem:
The size of the union is based on the size of the largest member of the union. Let's understand through an example. As we know, the size of int is 4 bytes, size of char is 1 byte, size of float is 4 bytes, and the size of double is 8 bytes.
Rearranging the fields in a struct can change the size of the struct . It is possible to minimize padding anomalies if the fields are arranged in such a way that fields of the same size are grouped together.
A structure can be nested inside a union and it is called union of structures. It is possible to create a union inside a structure.
So the maximum size is between 2^30 and (2^31 - 1). Then we can convert S30 to: typedef struct { S29 x; S29 y; uint8_t a[(2lu << 29) - 1]; } S30; and with that we determine that the maximum size is actually 2^31 - 1 == PTRDIFF_MAX on this implementation.
You could make a specialization of the struct for different cases of C:
template <int C = 3, typename T = unsigned char> union Color;
template <typename T>
union Color<3,T> {
  T v[3];
  struct {
    T r,g,b;
  };
};
template <typename T>
union Color<4,T> {
  T v[4];
  struct {
    T r,g,b,a;
  };
};
Note that anonymous structs are non-standard.
If using member functions is a possibility, I think that would be a better way to go:
template <int C,typename T>
class Color {
  public:
    using Values = T[C];
    Values &v() { return v_; }
    const Values &v() const { return v_; }
    T& r() { return v_[0]; }
    T& g() { return v_[1]; }
    T& b() { return v_[2]; }
    template <int C2 = C,
      typename = typename std::enable_if<(C2>3)>::type>
    T& a()
    {
      return v_[3];
    }
    const T& r() const { return v_[0]; }
    const T& g() const { return v_[1]; }
    const T& b() const { return v_[2]; }
    template <int C2 = C,
      typename = typename std::enable_if<(C2>3)>::type>
    const T& a() const
    {
      return v_[3];
    }
  private:
    Values v_;
};
You can then use it like this:
int main()
{
  Color<3,int> c3;
  Color<4,int> c4;
  c3.v()[0] = 1;
  c3.v()[1] = 2;
  c3.v()[2] = 3;
  std::cout <<
    c3.r() << "," <<
    c3.g() <<"," <<
    c3.b() << "\n";
  c4.v()[0] = 1;
  c4.v()[1] = 2;
  c4.v()[2] = 3;
  c4.v()[3] = 4;
  std::cout <<
    c4.r() << "," <<
    c4.g() << "," <<
    c4.b() << "," <<
    c4.a() << "\n";
}
                        Okay, so now @VaughnCato had this one out before me, but I'll still post my answer using std::enable_if. It declares Color as struct, because there is really no point in having a class when everything is public (and there is no good reason to declare the data member [v in the question] private), adds template aliases for a bit more syntactic sugar and uses static_assert to make sure the user doesn't use weird values for the template parameters. It also uses std::enable_if in a slightly different manner, which I would argue is a bit more readable.
#include <type_traits>
#include <cstdint>
template<unsigned nChans, typename T = std::uint8_t>
struct Color
{
    static_assert(nChans >= 3 || nChans <= 4,   "number of color channels can only be 3 or 4");
    // allow integral types only
    //static_assert(std::is_integral<T>::value,   "T has to be an integral type");
    // also allow floating-point types
    static_assert(std::is_arithmetic<T>::value, "T has to be an arithmetic (integral or floating-point) type");
    T data[nChans];
    T& r() { return data[0]; }
    T& g() { return data[1]; }
    T& b() { return data[2]; }
    //template<typename U = T, typename EnableIfT = std::enable_if<(nChans == 4), U>::type> // C++11
    template<typename U = T, typename EnableIfT = std::enable_if_t<(nChans == 4), U>> // C++14
    T& a() { return data[3]; }
    const T& r() const { return data[0]; }
    const T& g() const { return data[1]; }
    const T& b() const { return data[2]; }
    //template<typename U = T, typename EnableIfT = std::enable_if<(nChans == 4), U>::type>
    template<typename U = T, typename EnableIfT = std::enable_if_t<(nChans == 4), U>>
    T const& a() const { return data[3]; }
};
template<typename T = std::uint8_t> using RgbColor  = Color<3, T>;
template<typename T = std::uint8_t> using RgbaColor = Color<4, T>;
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With