Есть ли способ свернуть список инициализаторов вместо использования пакета параметров? Моя проблема в том, что у меня сильно перегруженный конструктор, и я хочу вызывать разные конструкторы в зависимости от того, использую ли я {}
или нет. Кажется, это отлично работает со списком инициализаторов, которому удается скрыть мой другой конструктор с одним аргументом, когда я использую {}
, в отличие от того, когда я просто создаю его с помощью ()
, но терпит неудачу, если я использую пакет параметров, который не скрывает мой другой конструктор аргументов.
Кроме того, я видел, как люди добавляют пустоту в свои выражения свертывания, что я не мог понять, когда ссылался на cppreference, и, похоже, это не имеет никакого значения в моей программе.
Редактировать: Как и просили, пример для иллюстрации проблемы:
#include <iostream>
#define USE_PARAMETER_PACK false
template<typename T>
struct Mega
{
int d;
T* arr;
Mega(int d) : d(d), arr(new T[d]) {}
Mega(int d, T u) : d(d), arr(new T[d])
{
std::fill(arr, arr + d, static_cast<T>(u));
}
#if USE_PARAMETER_PACK == true
template<typename ...Ts>
Mega(Ts&& ... vals) : d(sizeof...(Ts)), arr(new T[sizeof...(Ts)])
{
// fills the array with the arguments at compile time
int i = 0;
(void(arr[i++] = static_cast<T>(vals)), ...);
}
#else
template<typename U>
Mega(const std::initializer_list<U>& list) : d(list.size()), arr(new T[d])
{
auto it = list.begin();
//int i = 0;
//((arr[i++] = (list)), ...);
for (size_t i = 0; i < d; ++i, ++it)
arr[i] = static_cast<T>(*it);
}
#endif
};
template<typename T>
std::ostream& operator<<(std::ostream& os, const Mega<T>& m)
{
for (size_t i = 0; i < m.d; ++i)
os << m.arr[i] << "\t";
return os;
}
int main()
{
int* k;
k = new int[2];
k[0] = 2;
k[1] = 3;
Mega<int> l( k[0] );
// hides 1 argument ctor through {} invocation if using initializer_list,
// not so with parameter pack
Mega<int> m({ k[0]});
Mega<int> n(k[0], k[1]);
// hides 2 argument ctor through {} invocation if using initializer list
// not so with parameter pack
Mega<int> o({ k[0], k[1] });
std::cout << l << "\n";
std::cout << m << "\n";
std::cout << n << "\n";
std::cout << o << "\n";
return 0;
}
Обратите внимание на закомментированную часть, мне бы хотелось иметь возможность сделать что-то подобное, чтобы процесс заполнения списков параметров известного размера можно было выяснить во время компиляции, а не использовать цикл for.
Должен выводить несколько мусорных значений для первого cout и 2 для второго (по крайней мере, в MSVC2017 это так, не знаю, соответствует ли этот механизм скрытия стандарту). Обратите внимание, что если вы установите для определения значение true, вы можете использовать ctor пакета параметров, но он не может скрыть ctor с одним аргументом даже с синтаксисом {}
.
Edit2: дополнительно обновил код для максимального удобства, просто измените определение на true, чтобы увидеть, что пакет параметров не может скрыть конструкторы аргументов 1 и 2 с синтаксисом {}
, тогда как ctor списка инициализаторов справляется.
Ссылки: Использование списка инициализаторов: http://coliru.stacked-crooked.com/a/7b876e1dfbb18d73 Выход:
0 0
2
3 3
2 3
Использование пакета параметров: http://coliru.stacked-crooked.com/a/11042b2fc45b5259 Выход:
0 0
0 0
3 3
3 3
Что вы подразумеваете под «сворачиванием списка инициализаторов»?
@Barry Возможность свернуть список инициализаторов так же, как пакет параметров.
Термин void в сгибе предназначен для запрета вызова определенных пользователем перегрузок операторов (особенно оператора запятой), поскольку параметры функции не могут быть cv-квалифицированными как void. Вероятно, это не проблема для большинства пользователей, но необходима для правильного и безопасного кода библиотеки.
Почему бы вам не использовать std::tuple<> ??
@MichaëlRoy Не могли бы вы уточнить, как это поможет в этом случае? Будет ли синтаксис {}
скрывать другие ctor с кортежем?
Нет, мой комментарий больше касается устранения петли. Кортежи могут быть созданы из списка инициализаторов. Чистый результат был бы таким же, но с бонусной распаковкой, предоставляемой stl. Заметки в стороне: вам действительно следует сделать свой единственный аргумент ctor явным. А почему вы не используете std::vector??? По возможности избегайте прямых вызовов new и delete. Это также сделает вашу жизнь намного проще.
@MichaëlRoy О, хорошо, я тебя понял. Я знаю, как написать рекурсивный шаблон для списков инициализации, мне просто интересно, работает ли свертывание, что, я думаю, не основано на ответах. Кроме того, почему я должен запрещать неявные преобразования аргументов, особенно если типы совместимы? Я не использую std::vector, потому что остальная часть кода, которую вы здесь не видите, делает то, чего не может std::vector.
неявные конструкторы с одним аргументом — это ошибки, ожидающие своего появления. И я занимаюсь этим достаточно долго, чтобы заверить вас, что нет операции, которую вы можете сделать с массивом, которую вы не можете сделать с вектором. Цель состоит в том, чтобы устранить возможные ошибки. Хорошая практика кодирования — это предотвращение головной боли позже
@MichaëlRoy Да, мне, вероятно, следует сделать один аргумент ctor, а не явным, принимать только целочисленные типы без знака, а также иметь утверждение для 0 в качестве аргумента. С другой стороны, я должен, что неявные аргументы не всегда являются ошибкой, рассмотрим математический векторный класс (фиксированное измерение), который можно инициализировать из скаляра (все координаты становятся равными ему), он не должен быть явным для удобства (смотрите например, на gsl или hlsl). Что касается использования вектора, к нему прилагается слишком много ненужного в моем случае, и это на самом деле немного усложняет ситуацию для моего конкретного класса.
Конструкторы с одним аргументом сложны. Эмпирическое правило состоит в том, чтобы объявить их явными, если два типа не выражают одну и ту же идею. Например, конструктор, принимающий std::vector (подсказка к вашей проблеме), не должен быть явным. Но неявный ctor, принимающий одно целое, опасен, а также может сбивать с толку в точке вызова. my_mega = some_int;
может быть немного запутанным, а my_mega = Mega{some_int};
`не может быть. Поищите эту хорошую лекцию, которая затрагивает эту тему; «CppCon 2018: Титус Винтерс «Современный дизайн C++»
продолжение подсказки.... Имея ctor, принимающий стандартный вектор по стоимости, ваша строительная площадка станет auto my_mega = Mega<int>{{1, 2, 3}},
, и если бы у вас был вектор для хранения данных вашего объекта, вы даже могли бы (в зависимости от типа help во входном векторе ) для вызова конструктора перемещения вашего внутреннего вектора с предоставленным пользователем, тем самым сохраняя выделение памяти.
Я рассмотрю использование std::vector, я работал над полностью статической реализацией (со статическим размером) и в значительной степени сумел реализовать все операции как constexpr.
Списки инициализаторов имеют размер, выбираемый во время выполнения; их складывание — это дело цикла, а не обработчика шаблонов. (Я не могу точно сказать, является ли это отвечать чем-то большим, чем просто заголовок вопроса.)
@DavisHerring Это действительно хороший ответ, он подтверждает, что для них просто невозможно выполнить статическое складывание.
@lightxbulb: он не решает проблему сокрытия конструктора, но я опубликую его как ответ, если хотите.
@DavisHerring Я думаю, что часть конструктора очевидна, поскольку одна является объектом, а другая представляет собой набор аргументов, но не стесняйтесь добавлять ее в качестве ответа.
Поскольку длина initializer_list
равна Неизвестный во время компиляции (в частности, разные сайты вызовов с разным количеством элементов вызывают одну и ту же функцию), любая constexpr
обработка выполняется за невозможный. (Соответствующие функции-члены являютсяconstexpr
, но вы не можете использовать их как таковые в параметре функции.) В частности, для выражения fold требуется пакет параметров, размер которого всегда является выражением-константой.
Кто-то может помочь вам, если вы опубликуете минимальный воспроизводимый пример.