Инициализация скобок для унаследованного контейнера
#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 только говорит:
Имеет один или несколько конструкторов по умолчанию, каждый из которых либо тривиален, либо удален, и хотя бы один из них не удален.
Это не запрещает пользовательских конструкторов.