0

Consider this:

class Base {};    
class A : public Base {};
class B : public Base {};
class C : public Base {};
class D : public Base {};

class Obj
{
public:
    Obj(const Obj&);

private:
    A _a;
    B _b;
    C _c;
    D _d;
    Base *_current;
};

_current always points to one of _a, _b, _c, or _d. A, B, C, and D can have difference sizes. I want to implement Obj(const Obj&) so that _current of the copy points to the appropriate member in itself.

Is this approach safe:

Obj::Obj(const Obj& obj) :
    _a {obj._a},
    _b {obj._b},
    _c {obj._c},
    _d {obj._d}
{
    auto objAAddr = reinterpret_cast<const char *>(&obj._a);
    auto objCurAddr = reinterpret_cast<const char *>(obj._current);
    auto diff = objCurAddr - objAAddr;
    auto myAAddr = reinterpret_cast<char *>(&_a);
    _current = reinterpret_cast<Base *>(myAAddr + diff);
}

The "base" address could be something else than _a's here, like &_obj (and then apply difference to this).

Is there a better/cleaner alternative?

eepp
  • 7,255
  • 1
  • 38
  • 56
  • 1
    Out of curiosity, what are you trying to accomplish with this pattern? – Jive Dadson Mar 13 '18 at 04:57
  • 1
    Besides "Is it safe?" ask "Is it readable?" and "Is it maintainable?" – Jive Dadson Mar 13 '18 at 05:04
  • http://coliru.stacked-crooked.com/a/649b11b1b76c977c You can use a pointer-to-member-function to make the copy assignment trivial. Everything else becomes complicated, but... – Mooing Duck Mar 13 '18 at 05:12
  • I couldn't figure out how to do the same thing with a pointer-to-member-variable, because it didn't like that the variables had different types, because the cast from `Derived Obj::*` to `Base Obj::* is disallowed, because one could try to use it to _edit_ the raw data. Thus, I had to resort to generic getter functions. – Mooing Duck Mar 13 '18 at 05:13
  • Why not use std::variant? – xskxzr Mar 13 '18 at 08:55
  • Since C++17, using `reinterpret_cast` without `std::launder` likely [results in undefined behavior](https://stackoverflow.com/questions/27003727/does-this-really-break-strict-aliasing-rules). – xskxzr Mar 13 '18 at 09:09

2 Answers2

0

To calculate _current you should add to this the difference between obj._current and &obj. To properly calculate offsets you should use the special type uintptr_t. char * is not suitable since there are specific platforms with char is more than one byte.

#include <cstdint>
Obj::Obj(const Obj& obj) :
    _a {obj._a},
    _b {obj._b},
    _c {obj._c},
    _d {obj._d}
{
    _current = reinterpret_cast<Base *>(reinterpret_cast<uintptr_t>(this) +
        (reinterpret_cast<const uintptr_t>(obj._current) - 
            reinterpret_cast<const uintptr_t>(&obj)));
}

I would use C-style cast there:

Obj::Obj(const Obj& obj) :
    _a {obj._a},
    _b {obj._b},
    _c {obj._c},
    _d {obj._d}
{
    _current = (Base *)((uintptr_t)this +
        ((const uintptr_t)obj._current - (const uintptr_t)&obj));
}
273K
  • 29,503
  • 10
  • 41
  • 64
  • 1
    There are no platforms where `char` is more than one byte. `char` is _by definition_ one byte. It may be that a byte is 43 bits, but then a `char` is likewise 43 bits. – Mooing Duck Mar 13 '18 at 05:15
  • 2
    @MooingDuck modern byte is always 8 bits. It is documented in ISO/IEC 2382-1:1993. Do not mix byte and word. Char is not one byte by definition. Char is a type that sizeof(char) is 1. No bytes in the definition of char. – 273K Mar 13 '18 at 05:47
  • 1
    @S.M. There is no concept "modern byte" in C++. C++ standard only says byte can't be less than 8 bits. https://stackoverflow.com/questions/5516044/system-where-1-byte-8-bit – llllllllll Mar 13 '18 at 07:07
  • @liliscent Ok, actual byte instead of modern byte would be sound better. Yet again, C++ says char can not be less than 8 bits, `CHAR_BIT >= 8`, not byte. – 273K Mar 13 '18 at 07:40
  • 1
    @S.M. The sizeof operator yields the **number of bytes** in the object representation of its operand. [expr.sizeof](http://www.eel.is/c++draft/expr.sizeof#1) – llllllllll Mar 13 '18 at 07:47
  • § 5.3.3 from the C++ Spec reads: "The sizeof operator yields the number of bytes in the object representation of its operand. ... sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1." http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf (To be clear, you're right that _POSIX_ defines a byte to be 8 bits, and the C++ spec requires 8+ bits. I'm in agreement on those facts) – Mooing Duck Mar 13 '18 at 16:49
  • Look, in my context, I can live with `static_assert(CHAR_BIT == 8, "char type is 8 bit.");`. – eepp Mar 13 '18 at 18:30
  • Accepting this because it's cleaner than mine (at least the `uintptr_t` type). The tag solution would work, but in my scenario I actually have around 20 different choices and this one seems easier to extend too. – eepp Mar 13 '18 at 19:37
0

Ad hoc polymorphism will do it without brittle pointer arithmetic.

class Base {};    
class A : public Base {};
class B : public Base {};
class C : public Base {};
class D : public Base {};

class Obj
{
public:
    Obj(const Obj&);
    Obj() { set_current(aa);  }
private:
    A _a;
    B _b;
    C _c;
    D _d;
    Base *_current;
    enum base_tag {
        aa, bb, cc, dd
    } tag;
    void set_current(base_tag t) {
        tag = t;
        switch (tag) {
        case aa: _current = &_a; break;
        case bb: _current = &_b; break;
        case cc: _current = &_c; break;
        case dd: _current = &_d; break;
        default: throw("gulp");
        }
    }
};

Obj::Obj(const Obj& obj) :
    _a {obj._a},
    _b {obj._b},
    _c {obj._c},
    _d {obj._d}
{
    set_current(obj.tag);
}

int main() {
    Obj o1;
    Obj o2{ o1 };
}
Jive Dadson
  • 16,680
  • 9
  • 52
  • 65