0

I have the following two unions:

union rgb_color
{
  struct {
    float red;
    float green;
    float blue;
  };
  float raw[3];
};

union hsv_color
{
  struct {
    float hue;
    float saturation;
    float value;
  };
  float raw[3];
}

I'd like to add operator hsv_color() to rgb_color union, and operator rgb_color() to hsv_color union. Is there a way to do it? If I forward-declare hsv_color, the compiler throws the following error:

error: return type 'union hsv_color' is incomplete

The more I'm trying to implement this, the more I'm thinking I should just create two functions for conversion instead of using implicit cast operators. Still, I'd like to know if this is possible. Any suggestions?

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
xx77aBs
  • 4,678
  • 9
  • 53
  • 77
  • 2
    I hope you do not do type-puning with these unions: for example, writing into `raw` and then reading from `hue` and vice-versa is UB – Revolver_Ocelot Jun 19 '16 at 18:50
  • @Revolver_Ocelot: If by UB you mean undefined behaviour, then damn, didn't know that! Is that just in C++ or also in C? – xx77aBs Jun 19 '16 at 18:54
  • 1
    thats how unions work, if you need `raw` and `hue` at the same time, maybe it should not be a union in the first place – 463035818_is_not_an_ai Jun 19 '16 at 19:04
  • 2
    @xx77aBs [Type punning through union members is apparently legal in C99 and above](http://stackoverflow.com/questions/11639947/is-type-punning-through-a-union-unspecified-in-c99-and-has-it-become-specified?lq=1) – Revolver_Ocelot Jun 19 '16 at 19:07

2 Answers2

2

I would suggest the code below, but your basic assumption that struct of 3 floats will take exactly the same memory as array of 3 floats is probably wrong. There is structure member alignment. You'll need to use some "pragma pack" directive. Read here for example

struct hsv_color;

struct rgb_color
{
  union
  {
      struct 
      {
        float red;
        float green;
        float blue;
      }rgb;
      float raw[3];
  };
  operator hsv_color();
 };

struct hsv_color
{
    union
    {
      struct 
      {
        float hue;
        float saturation;
        float value;
      } hsv;
      float raw[3];
    };

    operator rgb_color();
};

rgb_color::operator hsv_color()
{
    hsv_color ret;

    // convert 'this' to hsv_color
    ret.hsv.hue = 0;//todo: perform appropriate calculation here
    ret.hsv.saturation = 0;//todo: perform appropriate calculation here
    ret.hsv.value = 0;//todo: perform appropriate calculation here

    return ret;
}

hsv_color::operator rgb_color ()
{
    rgb_color ret;

    // convert 'this' to rgb_color
    ret.rgb.red = 0;//todo: perform appropriate calculation here
    ret.rgb.green = 0;//todo: perform appropriate calculation here
    ret.rgb.blue = 0;//todo: perform appropriate calculation here

    return ret;
}
mvidelgauz
  • 2,176
  • 1
  • 16
  • 23
1

I would avoid a type-punning union as it becomes UB under the strict aliasing rule.

I take it that you are involving an array because you need to pass this array to an API, such as opengl.

In which case I would simply use the array and provide accessors to access r, g, b, h, s and v semantically.

You can provide the necessary conversion constructor with a forward-declaration:

// forward declare
struct hsv_color;

struct rgb_color
{
  rgb_color(float r, float g, float b)
    : raw { r, g, b }
  {}

  // forward declare
  rgb_color(hsv_color);

  float& r() { return raw[0]; }
  const float& r() const { return raw[0]; }
  float& g() { return raw[1]; }
  const float& g() const { return raw[1]; }
  float& b() { return raw[2]; }
  const float& b() const { return raw[2]; }

  const float* data() const  { return raw; }
  float* data()  { return raw; }

  private:
  float raw[3];
};

struct hsv_color
{
  hsv_color(float h, float s, float v)
   : raw { h, s, v }
  {}

  hsv_color(rgb_color rgb) { /*conversion here*/ }

  // more accessors here

  float raw[3];
};

// define

rgb_color::rgb_color(hsv_color hsv) { /* implement here */ }
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142