С++ 11 переменное количество аргументов, один и тот же конкретный тип

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

Я думал о чем-то подобном:

void func(int... Arguments)

Как правило, не работает рекурсивное статическое утверждение в типах?

Ответ 1

Возможное решение состоит в том, чтобы сделать тип параметра контейнером, который может быть инициализирован списком инициализатора скобок, например std::initializer_list<int> или std::vector<int>. Например:

#include <iostream>
#include <initializer_list>

void func(std::initializer_list<int> a_args)
{
    for (auto i: a_args) std::cout << i << '\n';
}

int main()
{
    func({4, 7});
    func({4, 7, 12, 14});
}

Ответ 2

Здесь версия, которая удаляет функцию из набора перегрузки, вместо того, чтобы давать static_assert. Это позволяет вам предоставлять другие перегрузки функции, которые могут использоваться, когда типы не все одинаковые, а не фатальные static_assert, которых нельзя избежать.

#include <type_traits>

template<typename... T>
  struct all_same : std::false_type { };

template<>
  struct all_same<> : std::true_type { };

template<typename T>
  struct all_same<T> : std::true_type { };

template<typename T, typename... Ts>
  struct all_same<T, T, Ts...> : all_same<T, Ts...> { };

template<typename... T>
typename std::enable_if<all_same<T...>::value, void>::type
func(T...)
{ }

Если вы хотите поддерживать идеальную пересылку, вы, вероятно, захотите разложить типы перед их проверкой, чтобы функция принимала сочетание аргументов lvalue и rvalue, если они имеют один и тот же тип:

template<typename... T>
typename std::enable_if<all_same<typename std::decay<T>::type...>::value, void>::type
func(T&&...)
{ }

В качестве альтернативы, если у вас есть общая цель для тестирования логического соединения, вы можете сделать это, используя std::is_same вместо написания собственного all_same:

template<typename T, typename... Ts>
typename std::enable_if<and_<is_same<T, Ts>...>::value, void>::type
func(T&&, Ts&&...)
{ }

Поскольку для этого требуется хотя бы один аргумент, вам также понадобится другая перегрузка для поддержки случая с нулевым аргументом:

void func() { }

Помощник and_ может быть определен следующим образом:

template<typename...>
  struct and_;

template<>
  struct and_<>
  : public std::true_type
  { };

template<typename B1>
  struct and_<B1>
  : public B1
  { };

template<typename B1, typename B2>
  struct and_<B1, B2>
  : public std::conditional<B1::value, B2, B1>::type
  { };

template<typename B1, typename B2, typename B3, typename... Bn>
  struct and_<B1, B2, B3, Bn...>
  : public std::conditional<B1::value, and_<B2, B3, Bn...>, B1>::type
  { };

Ответ 3

Я думаю, вы можете сделать это, указав конкретный тип при пережевывании своих аргументов из пакета аргументов. Что-то вроде:

class MyClass{};
class MyOtherClass{};

void func()
{
    // do something
}

template< typename... Arguments >
void func( MyClass arg, Arguments ... args )
{
    // do something with arg
    func( args... );
    // do something more with arg
}


void main()
{
    MyClass a, b, c;
    MyOtherClass d;
    int i;
    float f;

    func( a, b, c );    // compiles fine
    func( i, f, d );    // cannot convert
}

В общем случае void func( MyClass arg, Arguments ... args ) станет void func( arg, Arguments ... args ) с типом шаблона T.

Ответ 4

@Skeen Как насчет этого?

template <typename T>
void func_1(std::initializer_list<T>&& a) {
    // do something
} 

template <typename... T>
void func(T&&... a) {
    func_1({std::forward<T>(a)...});
} 

int main() {
    func(1, 2, 3);
    // func(1, 2, 3, 4.0); // OK doesn't compile
}

Ответ 5

Если вы не хотите использовать initializer_list/vector на основе скобок и хотите оставить аргументы раздельными в виде пакета аргументов, то ниже решение проверяет его во время компиляции, используя рекурсивный static_assert s:

#include<type_traits>

template<typename T1, typename T2, typename... Error>
struct is_same : std::false_type {};

template<typename T, typename... Checking>
struct is_same<T, T, Checking...> : is_same<T, Checking...> {}; 

template<typename T>
struct is_same<T,T> : std::true_type {};

template<typename... LeftMost>
void func (LeftMost&&... args)
{
  static_assert(is_same<typename std::decay<LeftMost>::type...>::value, 
                "All types are not same as 'LeftMost'");
  // ...
}

int main ()
{
  int var = 2;
  func(1,var,3,4,5);  // ok
  func(1,2,3,4.0,5); // error due to `static_assert` failure
}

Фактически это решение будет проверять все аргументы относительно первого аргумента. Предположим, что это было double, тогда все было бы проверено на double.

Ответ 6

Поскольку я не думаю, что видел это решение, вы могли бы написать определенную функцию для каждого типа (в вашем случае, просто int), а затем функцию пересылки, использующую типы вариационного аргумента.

Напишите каждый конкретный случай:

то для каждого конкретного случая:

// only int in your case
void func(int i){
    std::cout << "int i = " << i << std::endl;
}

Затем ваша функция переадресации:

template<typename Arg0, typename Arg1 typename ... Args>
void func(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){
    func(std::forward<Arg0>(arg0));
    func(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}

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

Используется следующим образом:

int main(){
    func(1, 2, 3, 4); // works fine
    func(1.0f, 2.0f, 3.0f, 4.0f); // compile error, no func(float)
}