0

Is there any compiler where the layout requirements for standard layout types do not also apply to trivially copyable types? In particular, the critical rule being that a pointer to the type is a pointer to its first member (where a base class would be considered coming prior to the derived class). That is, the address of the type would be the same address as its base type.

In code, is there any common compiler where the following would not actually work. It seems like common practice to me, thus I was surprised it wasn't standardized in C++11.

struct base { int a; /* is a trivial class*/ };
struct derived : public base { int b; /*still a trivial class*/ }

void copy( base * a, base * b, size_t len )
{
   memcpy( a, b, len );
}

...
derived d1, d2;
copy( &d1, &d2, sizeof(derived) );

I know for sure this works in GCC, and I believe it works in MSVC (though I may be wrong). In which non-historic compiler would the above not work as intended?


Extended Example

The above example shows the fundamental problem, but may not show the intent that would get one there. Here is a slightly more verbose example. Essentially anybody can call "send" which will queue up the message, then later something will dispatch each message by casting back to its real type.

struct header { int len, id; }
struct derived : public header { int other, fields; }

void send( header * msg )
{ 
   char * buffer = get_suitably_aligned_buffer( msg->len );
   memcpy( buffer, msg, msg->len ); 
}

void dispatch( char * buffer )
{
  header * msg = static_cast<header*>(buffer);
  if( msg->id == derived_id )
    handle_derived( static_cast<derived*>(msg) );
}


derived d;
d.len = sizeof(d);
d.id = deirved_id;
send( &d );

...
char * buffer = get_the_buffer_again();
dispatch( buffer );

It's still omitting many aspects, but the key parts are shown.

edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267
  • 6
    What makes you think that `memcpy`ing objects is "common practice" in C++? – Lightness Races in Orbit Mar 17 '12 at 17:20
  • 3
    I don't get why you would do that other just using an assignment operator? I VERY rarely see memcpy in C++ – 111111 Mar 17 '12 at 17:21
  • @LightnessRacesinOrbit, because I've seen it in a lot of code. Besides, the standard provides rules for `memcpy`ing objects, just doesn't guarantee this exact case. – edA-qa mort-ora-y Mar 17 '12 at 17:22
  • @111111, because you'd have several different objects derived from `base` and would like to treat them all as a similar binary blob. – edA-qa mort-ora-y Mar 17 '12 at 17:22
  • 4
    This just sounds like really bad design. – 111111 Mar 17 '12 at 17:24
  • 2
    @edA-qamort-ora-y: Sounds like you're looking at a lot of horrid code. Look at some good C++ code instead. – Lightness Races in Orbit Mar 17 '12 at 17:28
  • @edA-qamort-ora-y: No you wouldn't. They're not "similar binary blobs". They could be different sizes, if you've added members. Google "slicing problem c++" for the ramifications of that, or just see [this question](http://stackoverflow.com/q/274626/319403) for details. – cHao Mar 17 '12 at 17:30
  • I appreciate that you guys don't see the purpose of such code, that's fine. But the question is about common compiler guarantees in light of the standard's looser guarantees. – edA-qa mort-ora-y Mar 17 '12 at 17:40
  • @edA-qamort-ora-y: The compiler guarantees what it guarantees, which depends on how much it conforms to C++11. Considering no compiler has completely implemented C++11 yet, i'd say it's no guarantee at all. Even if what you want to do is defined behavior as far as c++ is concerned, which i'm not sure about anyway. It's not that we don't see a purpose; it's just that there are quite a number of reasons *not* to do it. – cHao Mar 17 '12 at 18:00
  • 1
    @111111: If it were "bad design", why did the C++ committee spend time and effort [*standardizing it*](http://stackoverflow.com/a/7189821/734069)? – Nicol Bolas Mar 17 '12 at 18:10

2 Answers2

1

I know for sure this works in GCC, and I believe it works in MSVC (though I may be wrong).

No you don't. You have run some examples that don't break on those compilers. That's different from knowing "for sure" anything.

Undefined behavior is undefined. The next version of GCC could break your code. The next version of Visual Studio could break your code. Indeed, compiling in release or with certain optimizations could break your code.

Following the standard is the only way to "know for sure" anything about what you get. Doing what you're doing is not implementation-defined behavior; it is undefined behavior. So you can't trust that you'll get a reasonable answer consistently even if it appears to work.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Yes, it is guaranteed for standard layout, but my type is only trivially copyable, thus the guarantee doesn't hold. That's why I'm surprised, the difference is so small I don't understand why the guarantee isnt' there for trivially copyable types. – edA-qa mort-ora-y Mar 17 '12 at 18:20
  • @edA-qamort-ora-y: In what way is your type *not* standard layout? – Nicol Bolas Mar 17 '12 at 18:21
  • The example is obviously quite basic, it doesn't show intent, just the underlying issue. The reason I don't use char * is because the actual `copy` function uses some data in the `base` class pointer (in my case, the length is actually a member variable). – edA-qa mort-ora-y Mar 17 '12 at 18:22
  • @Nicolas, it is not standard layout as it has non-static member variables in the base class and derived class (for example). – edA-qa mort-ora-y Mar 17 '12 at 18:23
  • @edA-qamort-ora-y: If your example doesn't actually show your problem, then please provide an example that *does*. It's hard to answer a question when you don't ask the right question. – Nicol Bolas Mar 17 '12 at 18:24
  • Yes, I know it is not standard, but the whole idea of standard-layout was non-standard for the entire history of C++ yet people relied on it nonetheless. That's why my question is whether there is actually a compiler which doesn't support the above code (compliant or not). – edA-qa mort-ora-y Mar 17 '12 at 18:39
  • @edA-qamort-ora-y: You haven't demonstrated that any of the compilers that you *claim* support it actually do support it. – Nicol Bolas Mar 17 '12 at 18:40
  • gcc supports this, as I am using it and don't have any problems (any optimization level). – edA-qa mort-ora-y Mar 17 '12 at 18:42
1

Yes, people have been doing this in C++ for as long as single inheritance has existed. Yes, it's essentially reasonable. No, it's not supported by the standard. Is it universally supported? Probably, but you already seem to know that's not the point. This kind of question is what standardization is supposed to eliminate.

For better or worse, C++ does provide a solution to this problem, albeit a distinctly less elegant one.

The problem is that nonstatic data members in the derived class don't necessarily follow the same padding after the base's members as if they were spliced directly into the base.

But a union of standard layout structs with a common initial sequence (purposely avoiding inheritance) does receive this guarantee.

struct header { int len, id; }

union derived {
    struct {
        header h;
        int payload;
    } fmt1;

    struct {
        header h; // repetitive
        double payload;
    } fmt2;

    // etc for all message types
};

The layout may actually differ when empty base classes are multiply included, especially if the first nonstatic data member is of the same type as an empty base class. The reason inheritance (still) can't do this is perhaps that they got tired of writing special cases about empty bases.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • I understand the layout may be different than having the pseudo-base as the fisrt member instead (via padding) but I'm fine with that. I was hoping to avoid the solution for the exact reason you indicate, it isn't elegant. Especially since I'm not trying to communicate with another language, this is strictly C++ to C++. Thanks. – edA-qa mort-ora-y Mar 18 '12 at 05:41