arun49 vs
arun49 vs

Reputation: 629

Field variables inside an Unnamed struct and union

What is the meaning of having some fields inside a struct inside a union in C++? I found this from a piece of code from the "Math for game developers" video series in YouTube:

private:
    union {
        struct {
            float m_x,m_Y;
        };
    };

Upvotes: 2

Views: 250

Answers (1)

5gon12eder
5gon12eder

Reputation: 25449

I have checked out revision 2d964cbb9e5065ec327ef6e2a5f086820ed024c1 and grepped for union and the closest match to your code sample I could find was in math/vector.h following line 56.

Cut down to the construct in question, we basically have this. (Note that this is different from the code you are showing.)

struct vec3
{
  union
  {
    struct
    {
      float x;
      float y;
      float z;
    };
    float v[3];
  };
};

This will allow us to refer to the elements of a vec3 either by name, as in

std::ostream&
operator<<(std::ostream& os, const vec3& v3)
{
  os << "(" << v3.x << ", " << v3.y << ", " << v3.z << ")";
  return os;
}

or using array syntax as in

std::ostream&
operator<<(std::ostream& os, const vec3& v3)
{
  os << "(";
  for (std::size_t i = 0; i < 3; ++i)
    os << (i ? ", " : "") << v3.v[i];
  os << ")";
  return os;
}

While this is about as good as you can get in C, personally, I think this is poor style C++. A more modern cleaner and equally efficient approach would use inline accessor functions and overload operator[].

#include <cstddef>
#include <stdexcept>

#ifndef NDEBUG
#  define DEBUG 1
#else
#  define DEBUG 0
#endif

class vec3
{
private:
  float data_[3];

public:

  constexpr vec3() noexcept : data_ {0.0f, 0.0f, 0.0f}
  {
  }

  constexpr vec3(const float x, const float y, const float z) noexcept : data_ {x, y, z}
  {
  }

  const float& x() const noexcept { return this->data_[0]; }
  const float& y() const noexcept { return this->data_[1]; }
  const float& z() const noexcept { return this->data_[2]; }

  float& x() noexcept { return this->data_[0]; }
  float& y() noexcept { return this->data_[1]; }
  float& z() noexcept { return this->data_[2]; }

  const float&
  operator[](const std::size_t i) const noexcept(!DEBUG)
  {
    if (DEBUG && i >= 3)
      throw std::invalid_argument {"vector index out of range"};
    return this->data_[i];
  }

  float&
  operator[](const std::size_t i) noexcept(!DEBUG)
  {
    if (DEBUG && i >= 3)
      throw std::invalid_argument {"vector index out of range"};
    return this->data_[i];
  }
};

While this is arguably a little redundant, it will pay off by giving us a very clean and efficient interface.

Using explicit member access:

std::ostream&
operator<<(std::ostream& os, const vec3& v3)
{
  os << "(" << v3.x() << ", " << v3.y() << ", " << v3.z() << ")";
  return os;
}

Using array syntax (optionally range-checked):

std::ostream&
operator<<(std::ostream& os, const vec3& v3)
{
  os << "(";
  for (std::size_t i = 0; i < 3; ++i)
    os << (i ? ", " : "") << v3[i];
  os << ")";
  return os;
}

Upvotes: 1

Related Questions