1

I have written MyString and MyStringConst class. Now I need from time to time pass MyString as MyStringConst, hence overload cast operator. I have written this

MyString::operator const MyStringConst &() const
{
    return reinterpret_cast<const MyStringConst &>(*this);
}

MyString has this data

char * str;
int length;
volatile int hashCode;
int bufferSize;

MyStringConst has this data

const char * c_str;
int length;
volatile int hashCode;

Plus there are some methods, that in both strings can recalculate hashCode.

Is this code correctly written. I have tested it on MSVC 2013 and it is working correctly, but I have no idea if it can be used in production code, that can be compiled with different compiler.

Martin Perry
  • 9,232
  • 8
  • 46
  • 114
  • 7
    if they're two totally different classes, you can't just reinterpret cast one to the other. data may be misaligned and so on... what's the point of MyStringConst. Why not just use const MyString? – thang Mar 01 '15 at 10:39
  • @thang MyString allocates a new string, MyStringConst not (just a wrapper around a pointer), but I need to compare them. – Martin Perry Mar 01 '15 at 10:46
  • In practice, it will PROBABLY work in all compilers you'll ever encounter in real life. But in strict langauge-lawyer terms, definitely not guaranteed to work. – Mats Petersson Mar 01 '15 at 10:48
  • If ALL you need is a comparison, then you could write a `compare` function that takes the four possible combinations [and that would just be a thin wrapper over a compare function that compares two `const char *` - possibly with the length (of the shorter or both) passed in too] – Mats Petersson Mar 01 '15 at 10:51
  • @MatsPetersson compare is inside class, I have no access to. It has method `bool IsSame(T & c)` where `T` is `MyStringConst`, because I have created member instance with that (`Foo collection;`) – Martin Perry Mar 01 '15 at 10:56
  • Then redesign that! ;) – Mats Petersson Mar 01 '15 at 10:59
  • @MatsPetersson Well. I can add ctor to MyStringConst, that will take MyString and create a new instance... I will have some performance penalty hit, but it will be cleaner solution :-) – Martin Perry Mar 01 '15 at 11:03
  • Doesn't sound like that bad a solution to me. If you are sure that nothing changes the `MyString` for the duration of the comparison [which would be bad if you cast it too], then you could just copy the pointer and length, which is probably not much overhead at all [especially if it's inlined, the compiler may be able to eliminate the extra copies altogether] – Mats Petersson Mar 01 '15 at 11:21
  • 1
    If I found this in production code as a replacement for `std::string` and `std::string const`, I'd certainly have a lot to talk about at the next code review... – Christian Hackl Mar 01 '15 at 13:31

1 Answers1

2

The common initial sequence of the data member is different and C++ makes no guarantee at all about the layout in this case, even if the types differ only by const qualification. Otherwise the guarantees for unions would effectively imply that the types need to have a common layout if they are standard-layout types (according to a note in 9.5 [class.union] paragraph 1).

In practice I would expect that the two types are laid out identical and that the reinterpret_cast works but there is no guarantee by the standard. Based on your comment MyStringConst merely holds a pointer to the string, i.e., instead of converting to references, I would just return a suitably constructed MyStringConst and avoid relying on undefined behavior:

MyString::operator MyStringConst() const {
    return MyStringConst(str, length);
}

The MyString object still has to live as long as the result from the conversion but this is no different to the case using reinterpret_cast.

BTW, the volatile on the hashCode is ill-advised: the only effect it will have is to slow down the program. I guess you are trying to use it to achieve synchronization between threads but in C++ volatile doesn't help with that at all: you get a data race when writing the member in one thread it is also accessed unsynchronized in another thread. You'd spell the member

std::atomic<int> hashCode;

instead.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 1
    Some nitpicking: In general, corresponding types **can** differ by const qualification and be part of the common initial sequence, according to [9.2p16]; they just need to be layout-compatible, and, according to [3.9p11], this includes types with different cv-qualifications. Here, however, the corresponding first members are *pointers to layout-compatible types*, and, to muddy the waters, the standard doesn't strictly call such pointers "layout-compatible". – bogdan Mar 01 '15 at 12:43
  • 1
    It does, however, say something that should be equivalent, in [3.9.2p3]: *[...] Pointers to layout-compatible types shall have the same value representation and alignment requirements [...]*. I think this should make this case pretty well-specified, even though in a strict word-by-word interpretation of the standard the two types don't have a common initial sequence. I'd venture to guess that the *intent* of the standard is that they do. – bogdan Mar 01 '15 at 12:47
  • Just found an additional indication: the footnote to [3.9.3p1]: *The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and non-static data members of unions.* – bogdan Mar 01 '15 at 13:09
  • I've just realized that what I'm quoting has mostly been added as a result of [DR1719](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1719), which was only included in N4296, after C++14. Maybe a DR-to-the-DR would be useful :-), in order to make pointers to layout-compatible types be layout-compatible themselves? – bogdan Mar 01 '15 at 13:25
  • @bogdan You can get fun cyclic dependencies that way. Consider: `struct A; struct B; struct A {B * pb;}; struct B {A * pa;};`. Are `A` and `B` layout-compatible? – T.C. Mar 01 '15 at 18:48
  • @T.C. Good point. Clearly it has to be more restrictive. But I still think that examples like the one in the OP are already, essentially, layout-compatible, even though the wording doesn't include them. How about this: in addition to the criteria in [3.9p11], *two types P1 and P2 are layout compatible if they are similar (4.4)*? (Aliasing through such types is already allowed by [3.10p10.3].) What do you think? – bogdan Mar 01 '15 at 19:30