0

I have some old code which allocates memory along the following lines:

class IntArrayAllocator
{
    int* const m_array;
public:
    IntArrayAllocator(int n) : m_array(new int[n]) {}
    ~IntArrayAllocator() { delete[] m_array; }

    operator int*() const { return m_array; }
};

int main()
{
    IntArrayAllocator allocator(10);
    for(int i=0;i<10;++i) {
        allocator[i] = i;
    }
...

I'd like to start modernising this code by slotting in a wrapper instead of the raw pointer, like so:

class IntArray
{
    int* const m_array;
public:
    IntArray(int* array) : m_array(array) {}
    int& operator[](int i) { return m_array[i]; }
    int operator[](int i) const { return m_array[i]; }
};

class IntArrayAllocator
{
...
    operator IntArray() const { return IntArray(m_array); } // replace previous operator int*
};

but then the user-defined conversion fails to work for the line

        allocator[i] = i;

with the error no match for ‘operator[]’ (operand types are ‘IntArrayAllocator’ and ‘int’). I have to use, e.g.,

        IntArray ia = allocator;
        ia[i] = i;

instead.

This error confused the heck out of me as I thought I could just replace a user-defined conversion to a built-in type with a user-defined conversion to my own type (slotting in classes that mimic built-in types feels natural in object-oriented programming). The closest I came to understanding why is this answer: it doesn't make sense to allow user-defined conversion to lead to a call arbitrary member functions on other types. And it seems this applies to operators too, even though this initially felt like it ought to work in this case? (I'm aware that a lot of people think very deeply about the details of this kind of thing and there are very probably good reasons why this isn't allowed.)

Assuming I've hit a brick wall with user-defined conversion, what would be a good way to approach this problem?


Edit: I should clarify that this question is supposed to be a minimal example of an issue I experienced in trying to solve a larger problem. It wouldn't be suitable, for example, to solve the larger problem using a std::vector, because, for example, I also need to be able to slice into an allocator, giving an int array object that refers to only part of the allocated memory. Also, the allocated memory won't always be allocated the same way: it may be from the heap, or the stack, returned from some 3rd party object, etc.

Community
  • 1
  • 1
TooTone
  • 7,129
  • 5
  • 34
  • 60
  • So I'm assuming that you do have reasons for it, but from the question as of now it isn't entirely clear. Why don't you skip the `IntArrayAllocator` class entirely and just use the `IntArray` directly? – villintehaspam Feb 07 '16 at 19:04
  • 1
    or you know, just use `std::array` or `std::vector`... – villintehaspam Feb 07 '16 at 19:05
  • @villintehaspam it's old code I'm upgrading. Long term I would like to move to an off-the-shelf solution. Nevertheless there are situations in which it can be useful to separate the decision of how a resource is allocated from how it can be used (e.g. allocate memory from heap, from stack, from memory-mapped file etc). – TooTone Feb 07 '16 at 19:06
  • right, so how about a search and replace from `IntArrayAllocator` to `std::vector`. Of course, your example may not show the entire functionality of your old class, but as it stands now that would probably be the easiest and best solution. Adding another wrapper class does not strike me as the obvious choice, but once again your actual use case may of course be slightly different than your example. – villintehaspam Feb 07 '16 at 19:12
  • Check out the vector interface, it should do what you want. Your attempt to modernize the code looks like a bad approach in comparison. In particular, user-defined conversions are probably a bad idea, in particular if they can be replaced with a simple function call making the conversion explicit. – Ulrich Eckhardt Feb 07 '16 at 19:13
  • I ought to clarify, I am trying to solve a more complicated problem in a large code base, and whilst `vector` would work for this particular example, it wouldn't help in for the more general problem. I wanted to highlight this particular problem I experienced without going into all the complexities of the more general problem. I'll try to clarify the question a little. (Of course, use vector could translate in my case, to use a single class.) – TooTone Feb 07 '16 at 19:23
  • 1
    Right, I kinda thought that there was more going on. Like Nicol hints at in his answer, you're not getting away from adding the `operator[ ]` member on `IntArrayAllocator` if you want to preserve the syntax. You can of course add something like a `slice(...)` or `data()` member to return your desired wrapper. The `[ ]` member could be implemented using `return this->data()[i];` or similar if you still need it but do not want to just dereference the memory directly. – villintehaspam Feb 07 '16 at 19:50

1 Answers1

1

Remember that name[...] is no different from name.operator[](...) (when name is not a pointer or array type). Type conversions don't get to happen on name when you call member functions on it.

So what you want (to be able to inject an operator[] into a type without adding such a member directly) is not possible. IntArrayAllocator must have an operator[] member function of its own. What it does is up to that, but it must have one.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982