Фон
Я работаю со встроенной платформой со следующими ограничениями:
- Нет кучи
- Нет библиотек Boost
- C++11 поддерживается
В прошлом я несколько раз сталкивался со следующей проблемой:
Создайте массив типа класса T, где T не имеет конструктора по умолчанию
В проект только недавно была добавлена поддержка C++11, и до сих пор я использовал специальные решения каждый раз, когда мне приходилось с этим сталкиваться. Теперь, когда доступен С++ 11, я решил попробовать сделать более общее решение.
Попытка решения
Я скопировал пример std::aligned_storage, чтобы придумать основу для моего тип массива. Результат выглядит следующим образом:
#include <type_traits>
template<class T, size_t N>
class Array {
// Provide aligned storage for N objects of type T
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
public:
// Build N objects of type T in the aligned storage using default CTORs
Array()
{
for(auto index = 0; index < N; ++index)
new(data + index) T();
}
const T& operator[](size_t pos) const
{
return *reinterpret_cast<const T*>(data + pos);
}
// Other methods consistent with std::array API go here
};
Это базовый тип — Array<T,N>
компилируется только в том случае, если T
конструируется по умолчанию. Я не очень хорошо знаком с упаковкой параметров шаблона, но просмотр некоторых примеров привел меня к следующему:
template<typename ...Args>
Array(Args&&... args)
{
for(auto index = 0; index < N; ++index)
new(data + index) T(args...);
}
Это определенно был шаг в правильном направлении. Array<T,N>
теперь компилируется, если переданные аргументы соответствуют конструктору T
.
Моя единственная оставшаяся проблема - это создание Array<T,N>
, где разные элементы в массиве имеют разные аргументы конструктора. Я подумал, что могу разделить это на два случая:
1 - Пользователь указывает аргументы
Вот мой удар по CTOR:
template<typename U>
Array(std::initializer_list<U> initializers)
{
// Need to handle mismatch in size between arg and array
size_t index = 0;
for(auto arg : initializers) {
new(data + index) T(arg);
index++;
}
}
Кажется, это работает нормально, за исключением необходимости обрабатывать несоответствие размеров между массивом и списком инициализаторов, но есть несколько способов справиться с этим, которые не важны. Вот пример:
struct Foo {
explicit Foo(int i) {}
};
void bar() {
// foos[0] == Foo(0)
// foos[1] == Foo(1)
// ..etc
Array<Foo,10> foos {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
}
2. Аргументы следуют шаблону
В моем предыдущем примере foos
инициализируется возрастающим списком, подобным std::iota
. В идеале я хотел бы поддерживать что-то вроде следующего, где range(int)
возвращает ЧТО-ТО, что может инициализировать массив.
// One of these should initialize foos with parameters returned by range(10)
Array<Foo,10> foosA = range(10);
Array<Foo,10> foosB {range(10)};
Array<Foo,10> foosC = {range(10)};
Array<Foo,10> foosD(range(10));
Поиск в Google показал мне, что std::initializer_list
не является «нормальным» контейнером, поэтому я не думаю, что у меня есть какой-либо способ заставить range(int)
возвращать std::initializer_list
в зависимости от параметра функции.
Опять же, здесь есть несколько вариантов:
- Параметры, указанные во время выполнения (возврат функции?)
- Параметры, указанные во время компиляции (
constexpr
возврат функции? шаблоны?)
Вопросы
- Есть ли какие-либо проблемы с этим решением до сих пор?
- Есть ли у кого-нибудь предложение создать параметры конструктора? Я не могу придумать решение во время выполнения или во время компиляции, кроме жесткого кодирования
std::initializer_list
, поэтому приветствуются любые идеи.