如何使用模板表达成员之间的约束

How to express constraints between members using templates?

本文关键字:成员 之间 约束 何使用      更新时间:2023-10-16

假设我有一个包含一堆成员的结构:

struct foo {
    int len;
    bar *stuff;
};

碰巧stuff将指向一个长为lenbars数组。我想把它编码成stuff的类型。所以类似于:

struct foo {
    int len;
    DependentLength<bar, &foo::len> stuff;
};

然后,我可以实现DependentLength,使其表现得像一个指向条形数组的指针,但当试图查看大于foo::len的索引时,它会断言。然而,我无法实现DependentLength<&foo::len>::operator[],因为运算符[]只接受一个参数,即索引,并且它需要知道"foo"对象的位置,以便取消引用成员指针模板参数并进行断言检查。

然而,我碰巧知道DependentLength在这里只能作为"foo"的成员使用。我真正想做的是告诉DependentLength在哪里可以找到相对于自身的len,而不是相对于foo指针。类似于DependentLength<(char*)&foo::stuff - (char*)&foo::len> stuff;,但这不是合法的C++。有没有一个好的或失败的邪恶的语言黑客可以使这项工作?

所以像DependentLength<(char*)&foo::stuff - (char*)&foo::len> stuff;

您要求模板根据运行时传递给它们的动态属性执行计算。。。这对模板不起作用,因为它们必须使用允许编译在compile time处创建模板参数所请求的代码的值进行实例化。因此,传递给模板的任何值都必须在编译时可解析,而不是在运行时。

您将不得不使用动态容器类型。例如,std::vector满足您的请求,如果您超出了底层容器的界限,std::vector::at()函数将抛出异常。不幸的是,它没有static_assert那么方便,但同样,在这种情况下使用static_assert是不可能的,因为您需要运行时检查边界。此外,std::vector还包含了operator[]的重载、迭代器、对其大小的查询等。

您可以告诉模板要用作长度的成员的偏移量。

template<typename T, typename LEN_T, ptrdiff_t LEN_OFFSET>
class DependentArray
{
public:
  T& operator[](LEN_T i_offset)
  {
    if (i_offset < 0) throw xxx;
    if (i_offset > this->size()) throw xxx;
    return this->m_pArr[i_offset];
  } // []
private:
  LEN_T& size()
  {
    return *reinterpret_cast<LEN_T*>(reinterpret_cast<char*>(this) + LEN_OFFSET);
  } // ()
private:
  T* m_pArr;
};
struct foo
{
  int len;
  DependentArray<bar, int, -sizeof(int)> stuff;
};

编辑2:

想了另一个解决方案。使用一个只适合foo的类来提供size字段的偏移量,并在定义了foo并可以计算偏移量后定义其方法:

#define MEMBER_OFFSET(T,M) 
  (reinterpret_cast<char*>(&reinterpret_cast<T*>(0x10)->M) - 
  reinterpret_cast<char*>(reinterpret_cast<T*>(0x10)))
template<typename T, typename LEN_T, typename SIZE_OFFSET_SUPPLIER> 
class FooDependentArray
{
public:
  T& operator[](LEN_T i_offset)
  {
    if (i_offset < 0) throw xxx;
    if (i_offset > this->size()) throw xxx;
    return this->m_pArr[i_offset];
  } // []
private:
  LEN_T& size()
  {
    const ptrdiff_t len_offest = SIZE_OFFSET_SUPPLIER::getOffset();
    return *reinterpret_cast<LEN_T*>(reinterpret_cast<char*>(this) + len_offset);
  } // ()
private:
  T* m_pArr;
};
struct FooSizeOffsetSupplier
{
    static ptrdiff_t getOffset();
};
struct foo
{
  int len;
  DependentArray<bar, int, FooSizeOffsetSupplier> stuff;
};
ptrdiff_t FooSizeOffsetSupplier::getOffset()
{
  return MEMBER_OFFSET(Foo,m_len) - MEMBER_OFFSET(Foo,m_pArr);
} // ()

这样就可以在foo中添加和删除成员。