2

Say I have a struct with a bunch of members:

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

As it so happens stuff will point to an array of bars that is len long. I'd like to encode this in stuff's type. So something like:

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

Then I could implement DependentLength to behave like a pointer to a bar array but that asserts when trying to looking at an index bigger than foo::len. However, I can't implement DependentLength<&foo::len>::operator[] because operator[] only takes one parameter, the index, and it needs to know the location of the 'foo' object in order to dereference the member pointer template parameter and do the assert check.

However, I happen to know that DependentLength will only ever be used here as a member of 'foo'. What I'd really like to do is tell DependentLength where to find len relative to itself, rather than relative to a foo pointer. So something like DependentLength<(char*)&foo::stuff - (char*)&foo::len> stuff;, but that's not legal C++. Is there a good or failing that evil language hack that could make this work?

Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165

2 Answers2

3

So something like DependentLength<(char*)&foo::stuff - (char*)&foo::len> stuff;

You're asking templates to perform calculations based on dynamic properties passed to them during run-time ... that won't work for templates since they must be instantiated with values that allow the compile to create the code requested by the template parameters at compile time. Thus any values passed to a template must be resolvable at compile-time, and not run-time.

You're going to have to use a dynamic container type. For instance, std::vector meets your request where the std::vector::at() function will throw an exception if you exceed the bounds of the underlying container. It's unfortunately not as convenient as a static_assert, but again, using static_assert is impossible for this situation since you need run-time checking for the bounds. Additionally, std::vector also incorporates an overload for operator[], iterators, queries for it's size, etc.

Jason
  • 31,834
  • 7
  • 59
  • 78
  • I'm not asking templates to operate on runtime values. &foo::len and &foo::stuff are both compile time constants. They're offsets, not absolute addresses. – Joseph Garvin Feb 13 '12 at 21:07
2

You can tell the template the offset of the member to use as length.

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

Edit 2:

Thought of another solution. Use a class that is good for foo only to supply the offset of the size field and define its method after foo is defined and offsets can be calculated:

#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);
} // ()

This makes it possible to add and remove members from foo.

selalerer
  • 3,766
  • 2
  • 23
  • 33
  • In your `size()` member function, how is it safe to cast an arbitrary value to a pointer type and then dereference it? Also it's never safe to return a reference to a temporary object. I believe you want to cast that pointer back to a `LEN_T` rather than a `LEN_T*`, and then simply return a `LEN_T` rather than `LEN_T&`. – Jason Feb 13 '12 at 21:09
  • That's what I have so far, but was hoping for a way that didn't require me to manually change the template parameters everytime I add new members to the struct. – Joseph Garvin Feb 13 '12 at 21:09
  • @Jason: It's not arbitrary. He's casting 'this' to char* so that pointer arithmetic will be in bytes, then backing up by 4 bytes to find the int that precedes DependentArray in the foo struct. – Joseph Garvin Feb 13 '12 at 21:11
  • Isn't the array of type `T`? In other words, let's say the address of `this` was `0x100`, and we subtracted 4-bytes to get an address of `0xFC` ... are you saying at the address of `0xFC` there is a valid `int` value that indicates the size of the array? Or is that address a value of type `T`? From what I can see in the code, `0xFC` is being cast to a `LEN_T*`, and then being dereferenced, meaning we are going to return a reference bound to the object contained at `0xFC`, not the actual value `0xFC`. – Jason Feb 13 '12 at 21:20
  • @JosephGarvin You're right, that's the drawback of this code. It's pretty easy to write a macro that calculates the offset of a member, but then you can't use it until the struct is fully defined. I started writing something that use a base class, SizeProviderBase with a pure virtual call getSize() but then I had to put it in a SizeProviderHolder so I'll have the "this" pointer implicitly and I needed a factory template with specialization for struct foo in order to create the right concrete type of SizeProvider, in short it became a big mass :-). I'll try to think of something more elegant. – selalerer Feb 14 '12 at 07:04