#c #templates #variadic-templates
#c #шаблоны #переменные-шаблоны
Вопрос:
В настоящее время я ищу решение для предоставления конструктору шаблонных классов и функции фиксированного шаблона в соответствии с количеством аргументов N
. Мой шаблон имеет вид template<typename T, unsigned int N>
. Идея заключается в том, что будет работать следующее:
MyClass<int, 4> object(1, 2, 3, 4);
В то время как следующее должно завершиться неудачей (и так далее):
MyClass<int, 4> object(1, 2, 3);
MyClass<int, 4> object(1, 2, 3, 4, 5);
Я думаю о чем-то подобном MyClass(T arg1, ..., T argN){...}
, но автоматически генерируемом. Я нашел только решения о том, как предоставить шаблону один или несколько аргументов. Но ничего по фиксированному количеству.
Я надеюсь, что кто-нибудь сможет мне помочь. Заранее спасибо!
Ответ №1:
Вы можете использовать промежуточный тип и использовать std::index_sequence
:
template <typename T, std::size_t>
using always_t = T;
template <typename T, typename Seq> struct MyClassImpl;
template <typename T, std::size_t ... Is>
struct MyClassImpl<T, std::index_sequence<Is...>>
{
MyClassImpl(always_t<T, Is>...);
};
template <typename T, std::size_t N>
using MyClass = MyClassImpl<T, std::make_index_sequence<N>>;
Или, аналогично, с более простым MyClassImpl
, и сложность перемещается в псевдоним:
template <typename ...Ts>
struct MyClassImpl
{
MyClassImpl(Ts... args);
};
template <typename T, std::size_t>
using always_t = T;
template <typename T, typename Seq>
struct MyClassAlias;
template <typename T, std::size_t... Is>
struct MyClassAlias<T, std::index_sequence<Is...>>
{
using type = MyClassImpl<always_t<T, Is>...>;
};
template <typename T, std::size_t N>
using MyClass = typename MyClassAlias<T, std::make_index_sequence<N>>::type;
Комментарии:
1. Я никогда раньше не видел такого подхода, но это умно. Мне это нравится.
2. @Jarod42 Спасибо, работа в порядке, но разве нет более чистого решения без полного обходного пути std lib? Проблема в том, что я пытаюсь написать проект, который легко понять новичкам. Так что для начала это решение было бы немного сложным…
3. @Ohjurot: Вы могли бы перейти к чему-то подобному
MyClassImpl2<int, int, int, int>
, что, вероятно, легче понять, чемMyClassImpl<int, std::index_sequence<0, 1, 2, 3>>
. и используйте другую форму псевдонима дляMyClass
.4. @Jarod42 Я посмотрю на это. Другой ответ также выглядит многообещающим. Я проведу еще несколько тестов на то, что лучше всего работает в моем случае.
5. @Ohjurot: добавлена вторая версия с более простым
MyClassImpl
(сложность теперь в псевдониме).
Ответ №2:
Вы можете добавить ограничение к перегрузке, например
template <typename T, std::size_t N>
struct MyClass {
// pre-C 20
template <typename... Args, std::enable_if_t<sizeof...(Args) == N, int> = 0>
MyClass(Argsamp;amp;... args) { /* ... */ }
// or alternatively, post-C 20,
template <typename... Args>
requires (sizeof...(Args) == N)
MyClass(Argsamp;amp;... args) { /* ... */ }
};
Обратите внимание, что в случае N == 1
then в нескольких случаях эти шаблоны будут предпочтительнее автоматически сгенерированного конструктора копирования. Если это потенциальная проблема, то вам нужно будет дополнительно ограничить шаблоны, чтобы предотвратить этот случай. Вот пример того, как вы могли бы это сделать.
Если вам не нужно использовать SFINAE для различения других конструкторов, вы могли бы вместо этого использовать static_assert
также:
template <typename... Args>
MyClass(Argsamp;amp;... args) {
static_assert(sizeof...(Args) == N);
// ...
}
Комментарии:
1. Следует соблюдать осторожность с конструктором пересылки, поскольку неконстантное значение lvalue выберет этот конструктор вместо конструктора копирования (когда N == 1).
2. @Jarod42 верно; Я уточню это в ответе, спасибо.