可以在模板外使用c ++ 11参数包吗?

时间:2013-12-05 18:25:04

标签: c++ templates c++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 ++版本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 :(
}

现在,我看到the accepted solution here建议使用类型特征和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) { }

5 个答案:

答案 0 :(得分:22)

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

这可能不太理想,但我认为随着C ++ 11的出现,你会经常看到这样的代码!

答案 1 :(得分:3)

为什么foo_impl解决方法,而不是直接在initialize_list<int>的签名中使用foo?它阐明了您接受所述类型的可变大小参数列表。

答案 2 :(得分:1)

您可以指定要显示的类型:

#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
}

答案 3 :(得分:0)

我意识到这是标记为C ++ 11,但C ++ 17 / 1z的功能在这里非常有用,所以我认为它的解决方案值得发布:

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

答案 4 :(得分:0)

与 Brian 的回答一样,我意识到这最初是为 C++11 设计的,但在 C++20 中,这可以使用概念以非常简单的方式解决:

#include <concepts>

void f(std::integral auto... ints)
{
    // ...
}

std::integral 接受任何整数类型,所以它更通用一点,如果可以接受的话。如果没有,您可以执行以下操作:

#include <concepts>

template<class T>
concept exactly_int = std::same_as<int,T>;

void f(exactly_int auto... ints)
{
   // ...
}

要对此多加一点解释,auto 本质上是一个隐式模板,它之前的名称限制了允许的类型。因此,在第一个示例中,将允许满足 std::integralintlongunsignedchar 等)的任何内容。第二个只允许 int,因为这是唯一满足定义的概念的类型。