2

First of all, this is more of a sanity check question to get some approval by people better-versed in the depths of the language standard than me.

Let's say I have the following types (though I left out any non-constructor and non-assignment member functions):

template<typename E> struct half_expr
{
};

class half : public half_expr<half>
{
public:
    half();
    explicit half(float);
    template<typename E> half(const half_expr<E>&);

    half& operator=(float);
    template<typename E> half& operator=(const half_expr<E>&);

private:
    half(std::uint16_t, bool);

    std::uint16_t data_;
};

Well, on any reasonable implementation a half shouldn't be anything else in memory than a plain std::uint16_t. But I'm interrested in the guarantees from the standard. Here is my rationale according to the C++98/03 defition of POD:

  • half cannot be a POD type, since it is has non-public fields, base-classes and user-defined constructors.

and the C++11 losened/extended definitions:

  • It should be trivially copyable, since it has only implicitly generated copy/move constructors/assignments, but only as long as those float and template versions don't count in any way, which I'm not completely sure of.

  • It should also be standard layout, since it only has a single private field of fundamental type and a single empty non-virtual base class (which should be POD in any standard, right?)

  • The only thing that hinders a POD-classification is that it is not trivially default constructible, which might IMHO be overcome by using C++11's half() = default syntax.

My quite simple question is just: Is my rationale entirely correct or are there any things I overlooked or misinterpreted in the definitions, especially in light of the user-defined constructors and assignments somehow hindering a classification as trivially copyable?

Note: Even if you feel the urge to delegate this to some possible duplicate about POD and standard-layout types (which I could prefectly understand), a simple comment answering my actual question would still be nice, since this is a mere sanity check, which might occur simple or superflous to you, but I just want to be on the safe side.

Jonas
  • 121,568
  • 97
  • 310
  • 388
Christian Rau
  • 45,360
  • 10
  • 108
  • 185
  • The type is trivially copyable (but not trivial) and standard-layout as you correctly analyzed, but that doesn't mean much in terms of guarantees for its size. – Luc Danton Aug 12 '12 at 16:06
  • 1
    Your assumptions can easily be checked with [`std::is_trivially_copyable`](http://en.cppreference.com/w/cpp/types/is_trivially_copyable), and [`std::is_standard_layout`](http://en.cppreference.com/w/cpp/types/is_standard_layout), and of course [`std::is_pod`](http://en.cppreference.com/w/cpp/types/is_pod). – Some programmer dude Aug 12 '12 at 16:26
  • 2
    @JoachimPileborg Assuming the implementation used perfectly adheres to the standard in this regard. – Christian Rau Aug 12 '12 at 16:31
  • I’m confused by your assignment operator. You allow `half x; half = 42;` but not `half x = 42;`. That is inconsistent and confusing and probably not what you intended. – Konrad Rudolph Aug 13 '12 at 15:04
  • @KonradRudolph It is as intended. I know the problem of not being able to write `half x = 42`, which is due to C++'s somehow weird behaviour when assignment-initializing (requiring an implicit conversion, instead of just resolving to the normal explicit `float` constructor), and of course I cannot make the conversion constructor implicit. But on the other hand I still want to support assignment of a `float` as far as possible. But your critique is correct and this inconsistency bugs me, too. Maybe I will even remove this assignment and require an explicit conversion in the future. – Christian Rau Aug 13 '12 at 15:37
  • @Christian No, you *can* make the conversion constructor implicit. And there is no weird behaviour here at all: you are *explicitly* telling C++ that you want to forbid `half x = 42` (that is the whole purpose of the `explicit` keyword). If you want to allow it, just make the conversion constructor implicit. – Konrad Rudolph Aug 13 '12 at 15:42
  • @KonradRudolph I *cannot* (in the sense of *want not*) make the float-to-half conversion implicit, since this conversion may reduce precision and should therefore only be explicitly enabled (whereas I regard assigning a float value to a half as explicit, since you, well, assign it to a half). – Christian Rau Aug 13 '12 at 15:49
  • @KonradRudolph On the other hand the half-to-float conversion (not in the example) needs to be (or *should be*) implicit, since it never reduces precision and enables properly typed mixed-operand arithmetics/functions. Making the float-to-half-conversion implicit too would create overload resolution problems, together with unexpected conversions. Not being able to say `half x = 42` is consequence I have to live with. – Christian Rau Aug 13 '12 at 15:49
  • @Christian You seem to be confusing two things, or I don’t understand you: you *already* allow the float-to-half implicit conversion under certain circumstances, via the assignment. – Konrad Rudolph Aug 13 '12 at 15:53
  • @KonradRudolph But you're free to suggest other ways of integrating the `half` type as good as possible into C++'s existing floating point types, enabling properly typed mixed-type operations and the like with out a dozen of overloads and without the neccessity for unneccessary explicit conversions (not meant sarcastic, construcive ideas are really welcome). – Christian Rau Aug 13 '12 at 15:53
  • @KonradRudolph I regard this assignment as an explicit conversion (even if the exact language term doesn't), since you explicitly assign the float to a variable of half-type (what conversion could possibly be done?). It would be nice to trigger a compiler warning (similar to double-to-float-conversion), but I dislike being actually not able to do the assignment. But I'm not completely decided on this yet, since it is indeed a bit inconsistent and maybe an error is better than no warning and I may remove this assignment operator. – Christian Rau Aug 13 '12 at 15:57
  • @KonradRudolph Sorry if that *"no sarcasm"* line from my previous comment came wrong, it wasn't meant in any way offensive. It was to state that my previous words were not meant to sound sarcastic, but as it stands it may be misinterpreted, I rephrased it a bit. – Christian Rau Aug 13 '12 at 16:00
  • @Christian Okay, we should stop the discussion here for space reasons but now I understand what you’re doing, and that it’s indeed intentional. Incidentally, I didn’t understand your comment as being sarcastic. – Konrad Rudolph Aug 13 '12 at 16:04

1 Answers1

6

Yes, half is standard-layout (9/7): the base class is empty, the derived class has the same access control for all non-static data, there's nothing virtual and no non-standard-layout bases or members, and the base is a different type from the first non-static data member.

The constructors and assignment operators that you define are not copy (or move) constructors and assignments, so they have no bearing on whether the class is trivially copyable.

The default constructor is non-trivial because it is user-provided (12.1/5), and so the class is not trivial.

The copies/moves are all trivial because the data member and the empty base have trivial copies/moves (12.8/12). Same for the destructor, and so the class is trivially copyable.

So I believe your analysis is correct - remove the no-arg constructor and you have a POD class in C++11 (but not in C++98).

As Luc says, there is no guarantee in the standard that a POD class contains no padding, even if it has only one data member.

In C++03, the empty base class optimization is permitted, not required, so a poor-quality but conforming C++03 implementation could certainly give you sizeof(half) == sizeof(half_expr<half>) + sizeof(uint16_t). It is guaranteed that sizeof(half_expr<half>) > 0, and if it's smaller than uint16_t you could reasonably expect padding too.

In C++11, the rules on layout-compatibility in effect require that the empty base class optimization is applied -- two standard-layout types in which one has a base class and the other does not, are layout compatible (9.2/17), and layout-compatible types can be read through a union (9.2/19) which means they must have the same layout. Padding is still permitted, but pointless.

The standard aside, your implementation may or may not be working from a C++ ABI that tells you what POD classes look like. If so, I would expect that although the ABI might not be up-to-date for C++11 yet, surely the implementation will make all standard-layout classes look the way POD classes are defined to look, regardless of whether they are also trivial (and it will do the EBC optimization). That's kind of the point of standard-layout: "classes laid out like C structs".

I think that implementations are also permitted to arbitrarily decide that all class types must be 4-aligned, in which case again you'd get padding. I don't think it's reasonable to expect this, though, since the only motivation I can think of for doing it is that you're on some bizarre architecture where 4-aligned pointers can be smaller than char*.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 3
    "Furthermore, the empty base class optimization is permitted, not required" No, it's *required* by the rules of standard layout in C++11. If a type is standard layout, then C++11 effectively requires that empty base or derived classes take up no room in the layout. Two types can be layout compatible with entirely different arrangements of base classes. The empty base optimization is not required for non-standard layout classes. – Nicol Bolas Aug 12 '12 at 22:45