Указание одного типа для всех аргументов, передаваемых в функцию с переменными числами или функцию с переменными шаблонами без использования массива, вектора, структур и т. Д.?
Я создаю функцию (возможно, функцию-член, не то, чтобы она имела значение... может быть, это имеет значение?), Которая должна принимать неизвестное количество аргументов, но я хочу, чтобы все они были одного типа. Я знаю, что могу передать массив или вектор, но я хочу иметь возможность принимать список аргументов напрямую, без дополнительной структуры или даже дополнительных скобок. Не похоже, что сами по себе переменные функции безопасны, и я не был уверен, как это сделать с шаблонными функциями. Вот по сути то, к чему я стремлюсь (скорее всего, не правильный код, и совсем не с целью получения списков драконов, смеется):
//typedef for dragon_list_t up here somewhere.
enum Maiden {
Eunice
, Beatrice
, Una_Brow
, Helga
, Aida
};
dragon_list_t make_dragon_list(Maiden...) {
//here be dragons
}
ИЛИ ЖЕ
template<Maiden... Maidens> dragon_list_t make_dragon_list(Maidens...) {
//here be dragons
}
ИСПОЛЬЗОВАНИЕ
dragon_list_t dragons_to_slay
= make_dragon_list(Maiden.Eunice, Maiden.Helga, Maiden.Aida)
;
Пробовал уже несколько вещей, похожих на выше, без кубиков. Предложения? Очевидные упущения, которые я, возможно, сделал? Я знаю, что это может быть не так уж и сложно:
dragon_list_t make_dragon_list(std::array<Maiden> maidens) {
//here be dragons.
}
dragon_list_t dragons_to_slay
= make_dragon_list({Maiden.Eunice, Maiden.Helga, Maiden.Aida})
;
но я бы предпочел сделать это первым способом, если это возможно.
15 ответов
Вы можете просто принять аргументы с помощью шаблона переменной и позволить проверке типов проверять достоверность позже, когда они будут преобразованы.
Вы можете проверить конвертируемость на уровне интерфейса функций, чтобы использовать разрешение перегрузки для отклонения откровенно неправильных аргументов, например, с помощью SFINAE.
template<typename R, typename...> struct fst { typedef R type; };
template<typename ...Args>
typename fst<void,
typename enable_if<
is_convertible<Args, ToType>::value
>::type...
>::type
f(Args...);
Для вашего случая использования, если вы знаете шаги, чтобы перейти от std::array<>
на ваш dragon_list_t
тогда вы уже решили это, хотя в соответствии с первым вариантом выше ("convert-позже"):
template<typename ...Items>
dragon_list_t make_dragon_list(Items... maidens) {
std::array<Maiden, sizeof...(Items)> arr = {{ maidens ... }};
// here be dragons
}
Если вы сочетаете это с вышеупомянутым is_convertible
подход, у вас есть шаблон раннего отклонения, который также разрешает перегрузки аргументов и отклоняет их, если это не применимо.
Если вы не используете template
для параметра, которого нет в пакете, функция variadic разрешит иметь все аргументы одного типа.
Вот пример для расширенного max
функция, которая принимает только int
s (или типы, конвертируемые в int
).
int maximum(int n) // last argument must be an `int`
{
return n;
}
template<typename... Args>
int maximum(int n, Args... args) // first argument must be an int
{
return std::max(n, maximum(args...));
}
Объяснение: Когда вы распаковываете пакет аргументов (args...
) компилятор ищет лучшую перегрузку. Если в пакете был только один параметр, то единственным кандидатом является maximum(int)
поэтому единственный параметр должен быть и типа int
(или конвертируется в int
). Если в пакете более одного элемента, то единственным кандидатом является maximum(int, typename...)
поэтому первый аргумент должен быть типа int
(или конвертируется в int
). По индукции легко доказать, что все типы в пачке должны иметь тип, преобразуемый в int
).
Поскольку вы включили тег C++0x, очевидным ответом будет поиск списков инициализаторов. Список инициализаторов позволяет вам указать количество аргументов для ctor, которые будут автоматически преобразованы в одну структуру данных для обработки ctor.
Их основное (исключительное?) Использование предназначено именно для той ситуации, которую вы упомянули, передавая несколько аргументов одного и того же типа для использования при создании некоторого списка / массива / другой коллекции объектов. Это будет поддерживаться (для одного примера) std::vector
так что вы можете использовать что-то вроде:
std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida};
создать вектор из трех dragon
объекты. Большинство (все?) Других коллекций будут включать в себя то же самое, поэтому, если вы действительно настаиваете на списке драконов, вы также сможете получить его довольно легко.
Недавнее предложение " Гомогенные функции с переменными числами" решает эту проблему, делая что-то вроде вашей первой конструкции легальной. За исключением, конечно, использования пакета параметров, вам нужно будет назвать его. Кроме того, точный синтаксис пока не очень конкретен.
Таким образом, согласно предложению это будет на самом деле законно (вы можете увидеть аналогичную конструкцию в параграфе "Представитель шаблона" в статье):
dragon_list_t make_dragon_list(Maiden... maidens) {
//here be dragons
}
Хотя вопрос помечен C++11, я думаю, что стоит добавить концептуальное решение C++17 +, потому что в GCC теперь есть поддержка, и вскоре последуют другие.
сначала определим простую концепцию
class mytype{};
template<typename T>
concept bool MyType = std::is_same<T, mytype>::value;
тогда просто используйте параметры шаблона variadic
template<MyType ... Args>
void func(Args &&... args){
// do something here
}
Гораздо проще с появлением концептов!
Используя С ++17, вы можете написать
template <class T, class... Ts, class = std::enable_if_t<(std::is_same_v<T, Ts> && ...)>
void fun(T x, Ts... xs)
{
}
Недавно мне нужно было ограничить пакет параметров только одним типом или, по крайней мере, преобразовать в этот тип. Я закончил тем, что нашел другой путь:
#include <type_traits>
#include <string>
template <template<typename> class Trait, typename Head, typename ...Tail>
struct check_all {
enum { value = Trait<Head>::value && check_all<Trait, Tail...>::value };
};
template <template<typename> class Trait, typename Head>
struct check_all<Trait, Head> {
enum { value = Trait<Head>::value };
};
template <typename ...Args>
struct foo {
// Using C++11 template alias as compile time std::bind
template <typename T>
using Requirement = std::is_convertible<double, T>;
static_assert(check_all<Requirement, Args...>::value, "Must convert to double");
};
int main() {
foo<int, char, float, double>();
foo<int, std::string>(); // Errors, no conversion
}
Что мне понравилось в этом решении, так это то, что я могу подать заявку check_all
к другим чертам тоже.
Приведенный ниже код будет работать с использованием концепций C++20. Скомпилировать с
-std=c++20
флаг.
#include <concepts>
#include <vector>
enum Maiden {
Eunice
, Beatrice
, Una_Brow
, Helga
, Aida
};
using dragon_list_t = std::vector<Maiden>;
dragon_list_t make_dragon_list(std::same_as<Maiden> auto... xs) {
return {xs...};
}
int main(int argc, char *argv[])
{
make_dragon_list(Helga,Aida,Aida);
return 0;
}
Это использует
std::same_as
концепция из стандартной библиотеки C++20. Вы также можете использовать
std::convertible_to
концепция, если вы хотите большей гибкости.
Это почти прямая альтернатива (Maiden...).
dragon_list_t make_dragon_list()
{
return dragon_list_t();
}
template<typename ...T>
auto make_dragon_list(Maiden first, T &&... other) -> decltype(make_dragon_list(std::forward<T>(other)...))
{
//here be dragons
return {first, std::forward<T>(other)...};
}
но он по-прежнему не поддерживает построение по нескольким переменным. Если Mained был классом, он может быть построен из двух переменных, когда
make_dragon_list({1,0})
будет работать, но
make_dragon_list({1,0}, {2,3})
не компилируется.
Вот почему это предложение так важно
Я бы постарался сделать вещи простыми, и самое простое решение, которое я могу придумать, это просто использовать старый простой вектор. Используя функции C++0x, вы можете получить синтаксис, который похож (даже если не совсем) на то, что вы хотите:
void foo( std::vector<int> const & v ) {
std::copy( v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ") );
}
int main() {
foo({ 1, 2, 3, 4, 5, 6 }); // note the extra {}
}
Это действительно зависит от того, что вы пытаетесь реализовать.
Обычно enum
указывает подтипы времени выполнения определенного класса или различенного объединения (boost:: option). Но в этом случае вы хотите передать enum
непосредственно. Более того, у вас есть ограниченный набор возможных значений, и каждый вызов функции образует подмножество. На самом деле то, что вы представляете, - это одно подмножество, а не несколько параметров.
Лучший способ представить подмножество конечного набора - это набор битов. Большие наборы должны использовать std::bitset
; маленькие наборы можно просто использовать unsigned long
,
enum Maiden_set {
Eunice = 1,
, Beatrice = 2
, Una_Brow = 4
, Helga = 8
, Aida = 16
};
dragon_list_t make_dragon_list(Maiden_set) {
//here be dragons
}
make_dragon_list( Eunice + Beatrice + Helga );
или, поскольку вы, кажется, хотите обрабатывать изменения во время компиляции,
template< int Maidens > // parameter is not a Maiden_set because enum+enum=int
dragon_list_t make_dragon_list() {
//here be dragons
}
make_dragon_list< Eunice + Beatrice + Helga >(); // + promotes each enum to int
Должно быть возможно генерировать полномочия 2 автоматически, используя operator+
перегружен на enum
тип. Но я не уверен, что на правильном пути.
Концепции C++20 делают его таким же красивым, как и
template<std::same_as<Maiden>... T>
dragon_list_t make_dragon_list(Maiden...) {
//here be dragons
}
Короче говоря, вы, вероятно, должны просто создать вектор. Это не так много, особенно если вы используете что-то вроде boost::list_of или список инициализатора C++0x. Синтаксические издержки минимальны и более гибки (вы можете передать список с рядом аргументов, известных только во время выполнения).
Если вы действительно хотите, вы можете использовать переменные параметры шаблона для этого:
// Using pass-by-value since I'm assuming it is primitive:
template< typename T, typename... Args>
void make_dragon_list_internal( dragon_list_t* dragon_list, T t, Args... args )
{
// add T to dragon_list.
make_dragon_list_internal( dragon_list, args... );
}
void make_dragon_list_internal( dragon_list_t* dragon_list )
{
// Finalize dragon_list.
}
template<typename... Args>
dragon_list_t make_dragon_list( Args... args )
{
dragon_list_t dragon_list;
make_dragon_list_internal( &dragon_list, args... );
return dragon_list;
}
Он безопасен для типов и расширяем (если хотите, вы можете использовать другие вещи, кроме драконов).
Я думаю, что следующий код полезен для вашего случая:
template <class...>
struct IsAllSame {};
template <class T, class B1>
struct IsAllSame<T, B1> {
static constexpr const bool kValue = std::is_same<T, B1>::value;
};
template <class T, class B1, class... Bn>
struct IsAllSame<T, B1, Bn...> {
static constexpr const bool kValue =
IsAllSame<T, B1>::kValue ? IsAllSame<T, Bn...>::kValue : false;
};
IsAllSame<int>::kValue == true
IsAllSame<bool, int>::kValue == false
IsAllSame<bool, int, int>::kValue == false
Более общий способ сделать это без дополнительных предположений (например, они относятся к одному определенному, конкретному типу) - это использовать современное предложение C++20+, которое для меня звучит как лучший вариант по сравнению с тем, чтобы это было встроено в язык. более интуитивно понятным способом.
В идеале вы могли бы сделать это:
template<typename... Ts> requires std::are_same_v<Ts...>
someFunction(Ts... ts)
... предполагая, что стандарт добавляет признак вариативного типа, что он уже должен был сделать.
Поскольку его еще нет, вот реализация вариативного:
template<typename... Ts>
struct are_same : std::integral_constant<bool, (std::is_same_v<typename std::tuple_element<0, std::tuple<Ts...>>::type, Ts> && ...)>
{};
template<typename... Ts>
inline constexpr bool are_same_v = are_same<Ts...>::value;
Без этой черты типа вы могли бы сделать прямую, длинную версию, вот так:
#include <type_traits>
#include <tuple>
template <typename... Ts>
requires (std::is_same_v<typename std::tuple_element<0, std::tuple<Ts...>>::type, Ts> && ...)
void myFunction(Ts... ts)
{
}
void TestFunction()
{
myFunction(4, 3, 5, 8); // satisfies constraint; all int
// myFunction(4.2, 3, 5, 8); // fails constraint; not all int
myFunction("bob", "sam", "suzy", "gertrude"); // satisfies constraint; all char const*.
}
Что оба они делают, так это используют возможности шаблона кортежа - которые являются метапрограмматическими по своей природе - для извлечения информации о типе первого элемента вариативного пакета, а затем сравнивают ее со всеми другими типами в пакете, чтобы убедиться, что они идентичны. Нет никаких накладных расходов во время выполнения. Это все время компиляции.
ПРИМЕЧАНИЕ: Чтобы сделать это еще лучше, я хотел представить это так:
template <typename... Us>
concept all_same = requires { (std::is_same_v<typename std::tuple_element<0, std::tuple<Us...>>::type, Us> && ...);};
// Which would allow the end user to very nicely do this:
template<all_same... Vs>
void myOtherFunction(Vs... vs)
{
}
К сожалению, это не работает. Он просто компилирует вызов функции независимо от того, все ли типы аргументов одинаковы. Я считаю, что это связано с тем, что типы вызываются против шаблонной концепции один за другим, а не все сразу. И так, если вы это сделаете:
void MyOtherTestFunction()
{
myOtherFunction("wtf", 28, 482.3);
}
Charconst * проверяется на соответствие концепции отдельно от 28 и т. Д. Поэтому каждый тип проверяется только на свой собственный кортеж из 1 элемента. Следовательно, это не соответствует ограничениям, чего мы не планировали.
Вариативные ограничения были бы полезны, но у нас, насколько мне известно, их нет в терминах концепций из-за этой проблемы. И, возможно, это к лучшему, поскольку это было бы злоупотреблением и вторжением в то, для чего предназначались концепции, то есть для ограничений отдельных типов, а не для групп типов. Вы можете, например, принять типы, которые соответствуют концепции, а также убедиться, что все они идентичны. В этом случае у вас будет что-то вроде
template<std::swappable... Ts> requires are_same<Ts...>
В
Возможно, есть хакерство для реализации вариативных концепций, но единственные хаки, которые я могу придумать, связаны с испорчением пользовательского интерфейса, который я здесь пытаюсь сделать более красивым и красивым, а не хуже. Например, вы могли бы указать, что пользовательский код явно использует кортежи или что-то в этом роде, но это не имеет смысла.
я верю