brace initialization for inherited pod
Asked Answered
L

4

13
#include <iostream>
#include <type_traits>


struct base_pod_t {
    unsigned x;
};

struct der_pod_t : public base_pod_t { };

int main()
{
    std::cout << "base_pod_t is POD: " << std::is_pod<base_pod_t>::value << std::endl;
    std::cout << "der_pod_t  is POD: " << std::is_pod<der_pod_t>::value << std::endl;
    base_pod_t b1 = {};     // OK
    base_pod_t b2 = {3};    // OK

    der_pod_t p1 = {};      // OK
//    der_pod_t p2 = {4};   // ERROR!
}

Last line results in error. How can I brace initialize der_pod_t with value?


It seems that even though it's a POD it tries to use constructor?


EDIT: As @Praetorian and @dyb suggested it is a POD thus result of std::is_pod<der_pod_t>::value is correct.

Lubricator answered 22/5, 2014 at 13:30 Comment(4)
you got a non-conformant compiler. g++ 4.8.2 produces a binary that says that der_pod_t is not a POD typeMangum
@BЈовић gcc version 4.8.2 20131212 (Red Hat 4.8.2-7) (GCC) said is a POD.Lubricator
Obviously a bug. gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) tells it is not.Mangum
It is a POD (as Praetorian said), but it's not an aggregate.Beckon
S
18

base_pod_t is an aggregate and the initialization you're performing is aggregate initialization.

From §8.5.1 [dcl.init.aggr]

1 An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

2 When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause. ...

However, der_pod_t is not an aggregate because it has a base class. It's a POD, and the same rules for list initialization do not apply. Now, when the compiler sees a non-empty braced-init-list it'll first search for a constructor that takes an initializer_list. If none are found it then attempts to match other constructors of the class. Since der_pod_t has no constructors that take a single int as argument, the error occurs.

Splanchnology answered 22/5, 2014 at 14:8 Comment(4)
I just read standard and it's exactly as you say. POD can't inherit (§9.0.10 §9.0.7 2nd bullet). Thanks.Lubricator
@Lubricator Which version of the standard are you quoting? I see nothing in N3936 that states der_pod_t is not POD. der_pod_t is standard layout because its base class is also standard layout. It satisfies both trivial class and standard layout class requirements, hence it is a POD.Splanchnology
It is a POD. I miss interpreted standard. Thanks.Lubricator
der_pod_t is an aggregate since C++17Froissart
H
11

As of CPP 17 this is allowed with a slight twist that you need additional {} within the initializer list for each base class. Note in the below example how {1,2} are enclosed in "{}" and initialize i, j while "3" initializes derived k.

struct base_pod
{
    int i, j;

};

struct der_pod : public base_pod
{
    int k;
};

der_pod dp{ {1 , 2}, 3 };

This works on GCC version 7.3.0 (not sure about earlier versions) but fails on VS17(v 15.9.4) and VS17 with "/std:c++17" flag so be mindful of your compiler support/flags.

relevant change proposal is here

Hydrograph answered 15/1, 2019 at 3:41 Comment(0)
F
0

I've been dealing with this problem today and found a solution for it, though I can't stress enough how dangerous this solution may be (see below why it's dangerous).

My particular problem was that I just wanted to extend a library struct with some methods of my own. I wanted to keep it POD with exactly the same layout as base since I wanted to use functions that take base as parameter.

The solution goes like this:

#include <iostream>
using namespace std;

struct BASE {
  int x, y;
};

struct FOO: BASE {
  void Foo() { x = y = 1; }
};

int main() {
  // const declaration
  const BASE a = { 0, 1 };
  const FOO &b = *reinterpret_cast<const FOO *> (&a);

  // non-const declaration
  BASE _a = { 0, 3 };
  FOO &c = *reinterpret_cast<FOO *> (&_a);

  cout << "base: " << a.x << ", " << a.y << endl;
  cout << "foo 1: " << b.x << ", " << b.y << endl;
  cout << "foo 2: " << c.x << ", " << c.y << endl;

  return 0;
}

However, note that this only works because the data layout between BASE and FOO is the same. Also only because I use pointers to cast to FOO type. In this case the type casting is done without any constructors, it just pretends that the memory is in correct format. If you try to reinterpret_cast without pointers, the compiler will instead try to construct a new object based on original.

See this answer for better explanation.

Unfortunately, there doesn't seem to be a nice one-liner for this. A proper macro for declarations seems in order.

Fishplate answered 30/3, 2015 at 13:20 Comment(0)
S
0

Try this;

struct A {
  float data;
  A() = default;
  A(float d) : data{d} {}
};

struct B : A {
  using A::A;
};

Test:

  A aa{1}; // OK
  B bb{1}; // OK
  std::cout << std::is_pod<A>::value << std::endl; // output 1
  std::cout << std::is_pod<B>::value << std::endl; // output 1

The output will show both of A and B are POD.

When https://en.cppreference.com/w/cpp/named_req/TrivialType only says:

Has one or more default constructors, all of which are either trivial or deleted, and at least one of which is not deleted.

It doesn't disallow custom constructors.

Sofia answered 11/7, 2018 at 11:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.