11

The first class will be used for private inheritance in order to ensure the exact same layout. This should make casting safe.

#include <iostream>
#include <string>

struct data_base
{
    data_base( int i, std::string&& s ) noexcept
        : i_{ i }
        , s_{ std::move( s ) }
    {}

    int i_;
    std::string s_;
};

In this trivial example, I print the int data member first followed by the std::string data member for instances of data<true>.

template<bool = true>
struct data : private data_base // inherits
{
    data( int i, std::string&& s ) noexcept
        : data_base( i, std::move( s ) )
    {}

    void print()
    {
        std::cout << "data<true> - " << i_ << s_ << '\n';
    }
};

However, the data<false> prints the std::string data member first, followed by the int data member.

template<>
struct data<false> : private data_base
{
    void print()
    {
        std::cout << "data<false> - " << s_ << i_ << '\n';
    }
};

Example:

int main()
{
    data<true> d{ 5, "abc" };
    d.print();
    ( ( data<false>& )d ).print();
}

Demo: http://coliru.stacked-crooked.com/a/8b1262afe23dc0a2

As the demo shows, even with the -fstrict-aliasing flag on, there's no warnings.

Now, since they have the same layout, I thought that I could just cast between the two types in order to get a different kind of static polymorphism; without the cost of a virtual function call.

Is this usage safe or am I triggering undefined behaviour?

user2296177
  • 2,807
  • 1
  • 15
  • 26
  • Not sure if `std::string` is guaranteed to be a standard-layout class – M.M Sep 08 '16 at 02:59
  • I think the `print` call counts as "using the stored value" for purposes of the strict aliasing rule, but not 100% sure – M.M Sep 08 '16 at 03:03
  • 2
    Any cast between unrelated types is, strictly speaking, causing undefined behavior. Also, in C++ it's recommended to use the C++ cast operators rather than C-style casts. – antred Sep 08 '16 at 03:38
  • @antred it's certainly not UB to cast between pointers or references to unrelated types. The problem arises with certain uses of the referred-to object. See C++14 [expr.reinterpret.cast]/7. Even unaligned casts are *unspecified* (as of C++14), not undefined as they are in C. – M.M Sep 08 '16 at 03:50

2 Answers2

2

It's more or less what's described here, the so called boost mutant idiom.

There it is said that (emphasis mine):

Boost mutant idiom makes use of reinterpret_cast and depends heavily on assumption that the memory layouts of two different structures with identical data members (types and order) are interchangeable. Although the C++ standard does not guarantee this property, virtually all the compilers satisfy it. Moreover, the mutant idiom is standard if only POD types are used.


Note: that page is pretty outdated, I don't know if the most recent revisions changed something about the guarantees above mentioned.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 1
    The question was not whether this will generally work. That is pretty much a given. Any reasonably sane compiler will handle this without nasty surprises. An insane amount of existing code that would break if this wasn't the case. The question was whether casts like these (and using the results of the cast) risk undefined behavior, and the answer is a resounding YES. – antred Sep 08 '16 at 10:01
  • Ok, now that I've reread your answer, it actually does provide an answer to the OP's question, so please disregard my comment. – antred Sep 08 '16 at 10:02
  • @antred No problem, critiques are always welcome. ;-) ... Note that you can delete your comments if you want. – skypjack Sep 08 '16 at 10:09
  • So it should work fine, but only with POD types. Thus my usage is wrong because `std::string` is not a POD? – user2296177 Sep 08 '16 at 15:44
  • @user2296177 It says that _the mutant idiom is standard if only POD types are used_, so it makes sense. Anyway, my knowledge doesn't go further enough, so I cannot say if your usage is actually wrong. I'm sorry. – skypjack Sep 08 '16 at 21:05
1

From [expr.reinterpret.cast]/11 in the language spec, you can cast a reference from one type to another (if you can cast a pointer to one to the other).

With your class layouts, both types have a common base class that holds all the data. The two derived types do not add any data members, nor do they add any virtual functions, so the object layout for both classes will be the same.

So the usage is safe if you use reinterpret_cast.

In this case, this is similar to casting to a reference the base class, then casting that reference to the other derived class.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • 1
    In my opinion, [expr.reinterpret.cast]/11 merely says that the cast can be performed but is not clear on whether using the result of the cast results in defined behavior or not. Seeing as a data is not a data, my expectation is that it would be undefined. – antred Sep 08 '16 at 05:27
  • 1
    @antred In general using the result of the cast would be undefined. But in this specific case that the OP is asking about, since `data` and `data` share a common base class with no virtual functions, and neither class adds any data or virtual functions, the memory layouts of both classes will be identical. – 1201ProgramAlarm Sep 08 '16 at 17:09