Указание одного типа для всех аргументов, передаваемых в функцию с переменными числами или функцию с переменными шаблонами без использования массива, вектора, структур и т. Д.? - PullRequest
36 голосов
/ 13 сентября 2010

Я создаю функцию (возможно, функцию-член, не то, чтобы она имела значение ... может быть, это имеет значение?), Которая должна принимать неизвестное количество аргументов, но я хочу, чтобы все они были одного типа.Я знаю, что могу передать массив или вектор, но я хочу иметь возможность принимать список аргументов напрямую, без дополнительной структуры или даже дополнительных скобок.Не похоже, что сами по себе переменные функции безопасны, и я не был уверен, как это сделать с шаблонными функциями.Вот по сути то, к чему я стремлюсь (скорее всего, не правильный код, и совсем не с целью получения списков драконов, смеется):

//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})
;

, но я бы предпочел сделать это первым способом, если это возможно.

Ответы [ 10 ]

30 голосов
/ 13 сентября 2010

Вы можете просто принять аргументы с помощью шаблона переменной и позволить проверке типов проверять достоверность позже, когда они будут преобразованы.

Вы можете проверить конвертируемость на уровне интерфейса функций, чтобы использовать разрешение перегрузки для отклонения откровенно неверных аргументов, например, используя 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-Later"):

template<typename ...Items>
dragon_list_t make_dragon_list(Items... maidens) {
    std::array<Maiden, sizeof...(Items)> arr = {{ maidens ... }};
    // here be dragons
}

Если вы объедините это сВ приведенном выше подходе is_convertible у вас есть шаблон раннего отклонения, который также разрешает перегрузку аргументов и отклоняет их, если это не применимо.

12 голосов
/ 14 сентября 2010

Если вы не используете template для параметра, не входящего в пакет, функция variadic разрешит иметь все аргументы одного типа.

Вот пример расширенной функции max, котораяпринимает только int с (или типы, конвертируемые в 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).

11 голосов
/ 13 сентября 2010

Поскольку вы включили тег C ++ 0x, очевидным ответом будет поиск списков инициализаторов .Список инициализатора позволяет вам указать количество аргументов для ctor, которые будут автоматически преобразованы в одну структуру данных для обработки ctor.

Их основное (эксклюзивное?) Использование предназначено именно для такой ситуацииЯ уже упоминал, передавая несколько аргументов одного и того же типа для использования при создании какого-либо списка / массива / другой коллекции объектов.Он будет поддерживаться (для одного примера) std::vector, поэтому вы можете использовать что-то вроде:

std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida};

для создания вектора из трех dragon объектов.Большинство (все?) Других коллекций будут включать в себя то же самое, поэтому, если вы действительно настаиваете на списке драконов, вы также сможете получить это довольно легко.

4 голосов
/ 10 июля 2012

Недавно мне нужно было ограничить пакет параметров только одним типом или, по крайней мере, преобразовать в этот тип. В итоге я нашел другой путь:

#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 и к другим чертам.

2 голосов
/ 16 июня 2016

Хотя вопрос помечен C ++ 11, я думаю, что стоит добавить концептуальное решение C ++ 17 +, потому что в GCC теперь есть поддержка, и вскоре последуют другие.

сначала определите простую концепцию

class mytype{};

template<typename T>
concept bool MyType = std::is_same<T, mytype>::value;

, затем просто используйте параметры шаблона переменной

template<MyType ... Args>
void func(Args &&... args){
    // do something here
}

Гораздо проще с появлением концепций!

1 голос
/ 14 сентября 2010

Я бы постарался сделать вещи простыми, и самое простое решение, которое я могу придумать, это просто использовать старый простой вектор. Используя функции 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 {}
}
1 голос
/ 13 сентября 2010

Короче говоря, вы, вероятно, должны просто создать вектор. Это не так много, особенно если вы используете что-то вроде 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;
}

Это типобезопасно и расширяемо (если хотите, вы можете использовать другие вещи, кроме драконов).

1 голос
/ 13 сентября 2010

Это действительно зависит от того, что вы пытаетесь реализовать.

Обычно 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. Но я не уверен, что я на правильном пути.

0 голосов
/ 01 апреля 2019

Недавнее предложение, Однородные функции с переменным числом , решает эту проблему, делая что-то вроде вашей первой конструкции легальной.За исключением, конечно, использования пакета параметров, вам нужно будет назвать его.Кроме того, точный синтаксис пока не выглядит очень конкретным.

Итак, согласно предложению это будет на самом деле законно (вы можете увидеть аналогичную конструкцию в параграфе «Представитель шаблона» в статье):*

dragon_list_t make_dragon_list(Maiden... maidens) {
    //here be dragons
}
0 голосов
/ 04 ноября 2015

Я думаю, что следующий код полезен для вашего случая:

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
...