7

I have a C datastructure representing a vector of boolean values; for reasons outside of my control the bools' are stored internally as integers with two magical values (not 0 and 1 ...) representing true and false. I have created a C++ class wrapping this C structure, and it works nicely. I have implemented the set()and get()methods as:

void Class::set(size_t index , bool value) {
     if (value)
        c_ptr[index] = SPECIAL_TRUE_VALUE;
     else
        c_ptr[index] = SPECIAL_FALSE_VALUE;
}

This works ok; but ideally I would like to overload operator[], however it is not clear to me how/if I can do that - due to special transformation between bool and the integer values?

user422005
  • 1,989
  • 16
  • 34
  • 1
    You need to read a bit about *proxy classes* – vsoftco May 06 '15 at 18:56
  • Yes - I have learned about proxy classes today - and overloading the assignment operator; which I have generally been a bit reluctant to do. – user422005 May 06 '15 at 20:41
  • This may or may not be viable for your solution, but you should think about the frequency of transformations versus the frequency of boolean accesses. If you are primarily using the set/get/[] functions and rarely using the SPECIAL_*_VALUE transformation, it may be easier to just store the bools, then transform only when needed. – nic May 06 '15 at 20:56

3 Answers3

7
struct pseudo_reference {
  operator bool()const&&{
    return c->get(index);
  }
  pseudo_reference operator=(bool b)&&{
    c->set(index, b);
    return {c,index};
  }

  // sometimes having named functions is useful:
  bool get() const&& {
    return std::move(*this);
  }
  void set(bool b)&& {
    std::move(*this) = b;
  }
  pseudo_reference()=delete;
private:
  Class* c;
  size_t index;
  pseudo_reference(pseudo_reference&&o)=default; // not exposed
  pseudo_reference(Class* pc, size_t i):c(pc),index(i){}
  friend class Class;
};

In Class:

pseudo_reference operator[](size_t i){
  return {this, i};
}
bool operator[](size_t i)const{
  return c_ptr[index] == SPECIAL_TRUE_VALUE;
}

I stored both a pointer and an index, so I avoid reimplementing the logic of get/set in my pseudo_reference. Such pseudo_references are likely to be short-lived, so size optimization probably isn't important.

I blocked all non-rvalue operations to discourage storing a pseudo_reference. You can make said operations non-rvalue restricted relatively harmlessly, but in my experience pseudo_references are values that behave like references, so it is better if they don't persist.

Someone can still store a pseudo_reference via auto&& x = c[33];, but using it without moveing it won't be possible. Hopefully that catches most error-prone uses of it. auto x = c[33]; won't work.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • In response to an edit attempt by @user1978011 : I made that private on purpose. The `friend class Class` should allow `Class` to construct `pseudo_reference`s. – Yakk - Adam Nevraumont May 06 '15 at 20:11
  • OK - I think I understand how it works? You will instantiate a pseudo_reference for every access to the `set()` method? That does seem a bit heavy weight? – user422005 May 06 '15 at 20:37
  • 1
    @user422005 it is a pointer (to `Class`) and a `size_t`. How is that heavy weight? A reference is typically implemented (if not inlined out of existence) as a pointer, so we are talking only a few more bytes that returning a reference to the data. A compiler worth its salt can inline it out of existence, as it only lives long enough to call `set` or `get` on the pointer anyhow. Do profiling with optimization against the raw call to `get` or `set` and I'd be shocked if there was much difference. small stack objects that go away immediately are **cheap**. – Yakk - Adam Nevraumont May 06 '15 at 20:44
  • @Yakk, can you do instead of `bool operator[](size_t i)const` something like `operator bool() const`? In this way, you convert the proxy to a `bool` whenever one deals with rvalue access. I remember seeing this kind of conversion used when read via a proxy, but want to double check. – vsoftco May 06 '15 at 21:11
  • @vsoftco A `const_proxy` is a different type than a `proxy` in general (it isn't just a `const proxy` -- sort of like pointers, a `const pointer` is different than a `pointer to const`). Rather than write a fancy class that just called `get` deferred, I made `[] const` return the value directly. Seems simpler, no? – Yakk - Adam Nevraumont May 06 '15 at 21:16
  • @Yakk ohhh ok, actually you do have an `operator bool()const` in `pseudo_reference`. I misread your code, I meant a conversion to `bool` in the proxy class, but you have it. Nice usage of rvalue overload `&&` also! – vsoftco May 06 '15 at 21:22
  • Sorry about that. I copied the code and it didn't compile with the private constructor. I messes up the friend declaration. Can you comment on the `const&&` syntax ? I have never seen it so far. Thx and my apologies again.. – user1978011 May 06 '15 at 21:28
  • @user1978011 make sure you compiler with C++11 support (`-std=c++11` for g++/clang++). That syntax means that the function is overloaded for rvalues (and actually disabled for lvalues). See e.g. http://stackoverflow.com/questions/21052377/whats-a-use-case-for-overloading-member-functions-on-reference-qualifiers – vsoftco May 06 '15 at 21:36
2

To implement operator[](), you need to return a proxy object that does the actual assignment when it appears on the left-hand-side of =:

struct proxy {
    proxy& operator=( bool value ) {
        c_.c_ptr[ index_ ] = value ? SPECIAL_TRUE_VALUE : SPECIAL_FALSE_VALUE;
        return *this;
    }
    operator bool() const { // for when it's just used normally, not =
        return c_ptr[ index ] == SPECIAL_TRUE_VALUE;
    }
private:
    Class &c_;
    size_t const index_;
    proxy( Class &c, size_t index ) : c_( c ), index_( index ) { }
    friend class Class;
}

class Class {
public:
    proxy operator[]( size_t index ) {
        return proxy( *this, index );
    }
    bool operator[]( size_t index ) const { // read-only access is easy
        return c_ptr[ index ] == SPECIAL_TRUE_VALUE;
    }
    // ...
};

Or something like that.

Paul J. Lucas
  • 6,895
  • 6
  • 44
  • 88
1

You can return a wrapper helper class which handles assignment for you.

struct WrapMe {
    c_ptr_T &value;
    WrapMe(c_ptr_T &_value) : value(_value) {}
    // handles assignment of bool values
    WrapMe & operator=(const bool b) {
       value = (b) ? SPECIAL_TRUE_VALUE : SPECIAL_FALSE_VALUE;
       return *this;
    }
    // handles cast to bool
    operator bool() const { return value == SPECIAL_TRUE_VALUE; }
};

class Class {
   WrapMe operator[](const int idx) { return WrapMe(c_ptr[idx]); }
   // ...
};
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
user1978011
  • 3,419
  • 25
  • 38