Подтвердить что ты не робот

Указание одного типа для всех аргументов, переданных в переменную функцию или функцию вариационного шаблона без использования массива, вектора, структур и т.д.?

Я создаю функцию (возможно, функцию-член, а не то, что она имеет значение... может быть, это так?), который должен принимать неизвестное количество аргументов, но я хочу, чтобы все они были одного типа. Я знаю, что могу передать массив или вектор, но я хочу иметь возможность принимать список аргументов напрямую без дополнительной структуры или даже дополнительных скобок. Это не похоже, что вариативные функции сами по себе являются типичными, и я не был уверен, как это сделать с помощью функций var/varadic. Здесь, по существу, то, к чему я стремлюсь (скорее всего, не правильный код, и полностью не с целью получения списков драконов, lol):

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

Здесь приведен пример расширенной функции 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).

Ответ 4

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

#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 к другим признакам.

Ответ 5

На самом деле это зависит от того, что вы пытаетесь реализовать.

Обычно enum указывает подтипы времени выполнения определенного класса или дискриминационный союз (boost:: variant). Но в этом случае вы хотите передать 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. Но я не уверен, что нахожусь на правильном пути.

Ответ 6

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

Он типирует и расширяется (вы можете заставить это взять вещи, отличные от драконов, если вам нравится).

Ответ 7

Я постараюсь, чтобы все было просто, и самое простое решение, о котором я могу думать, - это просто использовать простой старый вектор. Используя возможности С++ 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 {}
}

Ответ 8

Хотя вопрос отмечен как С++ 11, я думаю, что концептуальное решение на С++ 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
}

Намного легче с появлением понятий!

Ответ 9

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

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