#c #templates #tuples #std #template-meta-programming
#c #шаблоны #кортежи #std #шаблон-мета-программирование
Вопрос:
Я хочу перебирать типы кортежей, а не их элементы.
Представьте, что у вас есть универсальный интерфейс базового класса Controller
, и вы хотите иметь вектор указателей (я использую необработанный указатель вместо smart для удобства чтения): std::vector<Controller*> ctrls;
Теперь вы хотите добавить много реализаций Controller
интерфейса к этому вектору, чтобы вы могли сделать это:
ctrls.emplace_back(new MouseCtrl());
ctrls.emplace_back(new KeyboardCtrl());
ctrls.emplace_back(new ScreenCtrl());
(... and so on)
Но это некрасиво и не совсем расширяемо (как в принципе открытия-закрытия), поэтому было бы лучше иметь, например, кортеж: using Controllers = std::tuple<MouseCtrl, KeyboardCtrl, ScreenCtrl>;
а затем, в некоторой функции инициализации, перебирать эти типы:
for (T : Controllers> {
ctrls.emplace_back(new T());
}
Очевидно, что приведенный выше код не является допустимым синтаксисом C . Итак, вопрос в том, как это сделать?. Я просмотрел как std ::apply, так и std::visit / std::variant, но я понятия не имею, как это сделать (они перебирают элементы, а не типы).
Комментарии:
1. Типы в кортеже могут повторяться только во время компиляции.
2.
std::tuple
является типом значения. Если вы просто хотите по умолчанию сконструировать свои элементы, вы можете просто по умолчанию сконструироватьstd::tuple
. Если вам нужна более сложная инициализация, возникает вопрос, как это должно быть сделано в вашем случае, на который, боюсь, мы не можем ответить без дополнительной информации. В общем случае вы «перебираете» типы с помощью переменных шаблонов3. Повторяющийся вопрос на самом деле не отвечает на вопрос, если вы не можете создать кортеж. Вот решение, которое работает исключительно с типами: godbolt.org/z/rYfGoj (на который я не могу ответить на закрытый вопрос)
Ответ №1:
Вы можете использовать std::tuple_size
и std::tuple_element
, чтобы получить тип каждого элемента кортежа (это приведет к обратному порядку, но с небольшими изменениями вы можете изменить порядок):
#include <iostream>
#include <variant>
#include <vector>
using namespace std;
using Controllers = tuple<int, char, float>;
using ControllersContainer = vector<variant<int, char, float>>;
template <size_t N>
void add(ControllersContaineramp; ctrls)
{
ctrls.emplace_back(tuple_element_t<N-1, Controllers>{});
add<N - 1>(ctrls);
}
template <>
void add<0>(ControllersContaineramp; ctrls)
{
ctrls.emplace_back(tuple_element_t<0, Controllers>{});
}
int main()
{
ControllersContainer ctrls;
add<tuple_size_v<Controllers>>(ctrls);
}
Комментарии:
1. Важно отметить, что это будет работать только во время компиляции. Вероятно, это не то, чего хочет OP.
2. Вопрос был о переборе типов в кортеже. Кортеж является статическим типом — «итерация» возможна только во время компиляции. Хотя итерация здесь реализована с рекурсией.
3. @StPiere Это именно то, что я искал. Знаете ли вы, есть ли в стандартной библиотеке что-нибудь, что уже предоставляет такую функциональность, чтобы я мог пропустить добавление функции «добавить» и просто предоставить немного удовольствия от лямбды
ctrls.emplace_back(tuple_element_t<0, Controllers>{});
?4. @zupazit: я не знаю о прямом методе. Еще одной возможностью было бы перейти к make_index_sequence и tuple_size с помощью оператора запятой.