C++(以某种方式)将结构限制为父联合大小

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

本文关键字:结构 方式 C++      更新时间:2023-10-16

我正在尝试创建一个可变大小的颜色类 - 给定一个模板确定的值数组,我想为数组中的每个值创建命名别名,即:

template<int C = 3, typename T = unsigned char>
class Color {
public:
  union {
    T v[C];
    struct {
      T r, g, b, a;
    };
  };
};

但是,如果我尝试对 C=3 使用相同的类,则联合要求大小为 4 字节("a"成员)。或者,使用数学上表示的位域大小(名为 a 的结构,匿名 T 成员,大小在 C>3 处计算为 1),编译器发出宽容警告(不可抑制,根据 gcc 中,如何静音 -fallowive 警告?),这不适合更大规模的 API。

我将如何允许单个类处理不同数量的变量,同时保留每个变量的名称并且不实现递归包含宏魔术(尝试过这个,不应该这样做)。提前感谢!

编辑:为了澄清这个问题,以下任何一项的答案都将解决此问题:

  • 禁止 GCC 的允许错误(#pragma 忽略诊断不适用于宽容)
  • 将联合结构或子结构的最大大小设置为不超过 C 字节
  • 对于 C 字节未覆盖的成员,允许位域长度为 0(
  • GCC 允许位域长度的数学表达式,例如 (C-3> 0)?8:0;
  • 通过其他方式禁用 C 字节未覆盖的成员(即神话中的 static_if() )

您可以针对 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;
  };
};

请注意,匿名结构是非标准的。

如果可以使用成员函数,我认为这将是一个更好的方法:

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_;
};

然后,您可以像这样使用它:

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";
}

好的,现在@VaughnCato在我面前拿出了这个,但我仍然会使用 std::enable_if 发布我的答案。它将 Color 声明为结构体,因为当一切都是公共的时,拥有一个类真的没有意义(并且没有充分的理由声明数据成员 [问题中的 v] private),添加模板别名以获得更多语法糖并使用static_assert来确保用户不会对模板参数使用奇怪的值。它还以略有不同的方式使用std::enable_if,我认为这更具可读性。

#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>;