Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ (Somehow) limit struct to parent union size

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:

  • Suppress GCC's -fpermissive errors (#pragma diagnostic ignored doesn't work for permissive)
  • Set maximum size of union or child struct not to exceed C bytes
  • Allow bitfield length of 0 for members not covered by C bytes (GCC allows mathematical expressions for bitfield length, such as (C-3 > 0)?8:0; )
  • Disable members not covered by C bytes by some other means (ie, mythical static_if() )
like image 259
Precursor Avatar asked Mar 09 '15 19:03

Precursor


People also ask

What decides the size of a union in C?

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.

Can the size of a struct change?

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.

Can we use structure inside union in C?

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.

What is the maximum size of structure in C?

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.


2 Answers

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";
}
like image 158
Vaughn Cato Avatar answered Oct 14 '22 21:10

Vaughn Cato


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>;
like image 45
jplatte Avatar answered Oct 14 '22 21:10

jplatte