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

Можно ли использовать пакеты параметров С++ 11 вне шаблонов?

Мне было интересно, могу ли я иметь пакеты параметров, состоящие из одного, явно указанного типа. Например, что-то вроде этого:

#include <iostream>

using namespace std;

void show() { }

template<typename First, typename... Rest>
void show(First f, Rest... rest)
{
    cout << f << endl;
    show(rest...);
}

void foo(int f, int... args) // error
{
    show(f, args...);
}

int main()
{
    foo(1, 2, 3);
}

Проблема, с которой я столкнулась, - это определение foo(). С OS X clang++ version 5 (llvm 3.3svn) я получаю ошибку error: type 'int' of function parameter pack does not contain any unexpanded parameter packs.

Конечно, я могу его скомпилировать, перейдя на foo() в шаблон функции:

template<typename... Args>
void foo(int f, Args... args)
{
    show(f, args...);
}

Однако теперь foo() будет принимать int для первого параметра, а все остальное - для остальных. Например:

struct x { };
ostream& operator<<(ostream& o, x)
{
    o << "x";
    return o;
}

int main()
{
    foo(1, 2, x(), 3); // compiles :(
}

Теперь я видел принятое решение здесь, в котором предлагается использовать черты типа и std::enable_if, но это громоздко. Они также предложили использовать std::array, но я думаю, что простой std::initializer_list работает просто отлично и выглядит более чистым, например:

void foo_impl(initializer_list<int> ints)
{
    for(int i: ints)
        cout << i << endl;
}

template<typename... Args>
void foo(int f, Args... args)
{
    foo_impl({f, args...});
}

struct x { };
ostream& operator<<(ostream& o, x)
{
    o << "x";
    return o;
}

int main()
{
    foo(1, 2, 3);
    foo(1, 2, x(), 3); // no longer compiles
                       // we also get an error saying no known conversion from 'x' to 'int' :)
}

Так что аккуратно. Но вопрос остается, нужно ли это? Не существует способа определить функцию без шаблона, которая принимает пакет параметров определенного типа? Вот так:

void foo(int... args) { }
4b9b3361

Ответ 1

void foo(int... args) {}

Нет, вы не можете это написать.

Но вы можете иметь такой же эффект при таком подходе:

template<typename ...Ints>
void foo(Ints... ints) 
{
   int args[] { ints... }; //unpack ints here
   //use args
}

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

Вы также можете использовать static_assert, чтобы гарантировать, что все Ints действительно int, если вы хотите это поведение:

template<typename ...Ints>
void foo(Ints... ints) 
{
   static_assert(is_all_same<int, Ints...>::value, "Arguments must be int.");

   int args[] { ints... }; //unpack ints here
   //use args
}

Теперь вы должны реализовать мета-функцию is_all_same, которую не сложно реализовать.

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

Для большой работы, которую вы можете использовать с переменными аргументами, вам даже не нужно хранить в массиве args[], например, если вы хотите напечатать аргументы до std::ostream, тогда вы можете просто сделать это как:

struct sink { template<typename ...T> sink(T && ... ) {} };

template<typename ...Ints>
void foo(Ints... ints) 
{
    //some code

     sink { (std::cout << ints)... };
}

Здесь вы создаете временный объект типа sink, чтобы использовать распаковку аргументов шаблона с помощью синтаксиса list-initialization.

Наконец, вы можете просто использовать std::initializer_list<int>:

void foo(initializer_list<int> const & ints) 
{

}

Или std::vector<int>, если вам понадобится векторное поведение внутри foo(). Если вы используете любой из них, вы должны использовать {} при вызове функции как:

f({1,2,3});

Это может быть не идеально, но я думаю, что с появлением С++ 11 вы увидите такой код очень часто!

Ответ 2

Почему обходной путь foo_impl, а не просто использовать initialize_list<int> в foo подпись напрямую? Он поясняет, что вы принимаете список аргументов переменного размера указанного типа.

Ответ 3

Вы можете указать тип, который хотите показать:

#include <iostream>

template<typename T>
void show_type() {}

template<typename T, typename... Rest>
void show_type(const T& x, Rest... rest)
{
    std::cout << x << std::endl;
    show_type<T>(rest...);
}

template<typename... Args>
void foo(int x, Args... args)
{
    show_type<int>(x, args...);
}

struct X { };
std::ostream& operator<<(std::ostream& o, X)
{
    o << "x";
    return o;
}


int main()
{
    foo(1, 2, 3);
    foo(1, 2, 3.0); // Implicit conversion
    // or just
    show_type<int>(1, 2, 3);
    foo(1, 2, X()); // Fails to compile
}

Ответ 4

Я понимаю, что это отмеченный С++ 11, но функции С++ 17/1z работают фантастически здесь, поэтому я решил, что его решение стоит опубликовать:

template<typename... Ints>
void foo(Ints... xs)
{
    static_assert(std::conjunction<std::is_integral<Ints>...>::value);
    (std::cout << xs << '\n', ...);
}