Инициализация скобок для унаследованного контейнера

#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!
}

Последняя строка приводит к ошибке. Как я могу скобки инициализировать der_pod_t со значением?


Кажется, что даже если это POD, он пытается использовать конструктор?


РЕДАКТИРОВАТЬ: Как @Praetorian и @dyb предположили, что это POD, поэтому результат std::is_pod<der_pod_t>::value является правильным.

Ответ 1

base_pod_t является агрегатом, и инициализация, которую вы выполняете, представляет собой агрегатную инициализацию.

Из §8.5.1 [dcl.init.aggr]

1 Агрегат - это массив или класс (раздел 9) без конструкторов, предоставляемых пользователем (12.1), без частных или защищенных нестатических данных (раздел 11), без базовых классов (п. 10) и нет виртуальные функции (10.3).

2 Когда агрегат инициализируется списком инициализаторов, как указано в 8.5.4, элементы списка инициализаторов берутся как инициализаторы для членов агрегата, увеличивая индекс или порядок членов. Каждый член инициализируется копией из соответствующего предложения инициализатора....

Однако der_pod_t не является агрегатом, потому что он имеет базовый класс. Это POD, и те же правила для инициализации списка не применяются. Теперь, когда компилятор увидит непустой бит-init-list, он сначала ищет конструктор, который принимает initializer_list. Если ни один не найден, он пытается сопоставить другие конструкторы класса. Поскольку der_pod_t не имеет конструкторов, которые принимают один аргумент int, возникает ошибка.

Ответ 2

Начиная с CPP 17 это допускается с небольшим изменением, которое требует дополнительного {} в списке инициализатора для каждого базового класса. Обратите внимание на приведенный ниже пример, как {1,2} заключены в "{}" и инициализируют i, j, в то время как "3" инициализирует производное k.

struct base_pod
{
    int i, j;

};

struct der_pod : public base_pod
{
    int k;
};

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

Это работает на GCC версии 7.3.0 (не уверен в более ранних версиях), но не работает на VS17 (v 15.9.4) и VS17 с флагом "/std: С++ 17", поэтому помните о поддержке/флагах вашего компилятора.

соответствующее предложение об изменении здесь

Ответ 3

Сегодня я столкнулся с этой проблемой и нашел для нее решение, хотя я не могу достаточно подчеркнуть, насколько опасно это решение (см. ниже, почему это опасно).

Моя особая проблема заключалась в том, что я просто хотел расширить библиотечную структуру с помощью некоторых моих методов. Я хотел сохранить его POD с точно такой же компоновкой, что и базовая, поскольку я хотел использовать функции, которые берут базу в качестве параметра.

Решение выглядит следующим образом:

#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;
}

Однако обратите внимание, что это работает только потому, что расположение данных между BASE и FOO одинаково. Также только потому, что я использую указатели для создания типа FOO. В этом случае литье типов выполняется без каких-либо конструкторов, оно просто притворяется, что память находится в правильном формате. Если вы попробуете reinterpret_cast без указателей, вместо этого компилятор попытается создать новый объект на основе оригинала.

Подробнее см. этот ответ.

К сожалению, для этого не кажется приятным однострочным. Правильный макрос для деклараций выглядит в порядке.

Ответ 4

Попробуй это;

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

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

Тестовое задание:

  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

Вывод покажет, что и A, и B являются POD.

Когда https://en.cppreference.com/w/cpp/named_req/TrivialType только говорит:

Имеет один или несколько конструкторов по умолчанию, каждый из которых либо тривиален, либо удален, и хотя бы один из них не удален.

Это не запрещает пользовательских конструкторов.