如何编写一个接受可变数量参数的函数?这可能吗,怎么做?
在 C++11 中,您有两个新选项,正如 Alternatives 部分 中的 Variadic arguments 参考页面所述:
可变参数模板也可用于创建采用可变数量参数的函数。它们通常是更好的选择,因为它们不对参数的类型施加限制,不执行整数和浮点提升,并且是类型安全的。 (C++11 起) 如果所有变量参数共享一个公共类型,则 std::initializer_list 提供了一种方便的机制(尽管使用不同的语法)来访问变量参数。
下面是一个显示两种选择的示例 (see it live):
#include <iostream>
#include <string>
#include <initializer_list>
template <typename T>
void func(T t)
{
std::cout << t << std::endl ;
}
template<typename T, typename... Args>
void func(T t, Args... args) // recursive variadic function
{
std::cout << t <<std::endl ;
func(args...) ;
}
template <class T>
void func2( std::initializer_list<T> list )
{
for( auto elem : list )
{
std::cout << elem << std::endl ;
}
}
int main()
{
std::string
str1( "Hello" ),
str2( "world" );
func(1,2.5,'a',str1);
func2( {10, 20, 30, 40 }) ;
func2( {str1, str2 } ) ;
}
如果您使用 gcc
或 clang
,我们可以使用 PRETTY_FUNCTION magic variable 来显示函数的类型签名,这有助于理解正在发生的事情。例如使用:
std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;
示例中的可变参数函数将导致 int 以下 (see it live):
void func(T, Args...) [T = int, Args = <double, char, std::basic_string<char>>]: 1
void func(T, Args...) [T = double, Args = <char, std::basic_string<char>>]: 2.5
void func(T, Args...) [T = char, Args = <std::basic_string<char>>]: a
void func(T) [T = std::basic_string<char>]: Hello
在 Visual Studio 中,您可以使用 FUNCSIG。
更新前 C++11
在 C++11 之前,std::initializer_list 的替代方案是 std::vector 或其他 standard containers 之一:
#include <iostream>
#include <string>
#include <vector>
template <class T>
void func1( std::vector<T> vec )
{
for( typename std::vector<T>::iterator iter = vec.begin(); iter != vec.end(); ++iter )
{
std::cout << *iter << std::endl ;
}
}
int main()
{
int arr1[] = {10, 20, 30, 40} ;
std::string arr2[] = { "hello", "world" } ;
std::vector<int> v1( arr1, arr1+4 ) ;
std::vector<std::string> v2( arr2, arr2+2 ) ;
func1( v1 ) ;
func1( v2 ) ;
}
variadic templates 的替代方案是 variadic functions,尽管它们不是 type-safe 并且通常是 error prone and can be unsafe to use,但唯一的其他潜在替代方案是使用 默认参数,尽管它的用途有限。下面的示例是链接参考中示例代码的修改版本:
#include <iostream>
#include <string>
#include <cstdarg>
void simple_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
while (*fmt != '\0') {
if (*fmt == 'd') {
int i = va_arg(args, int);
std::cout << i << '\n';
} else if (*fmt == 's') {
char * s = va_arg(args, char*);
std::cout << s << '\n';
}
++fmt;
}
va_end(args);
}
int main()
{
std::string
str1( "Hello" ),
str2( "world" );
simple_printf("dddd", 10, 20, 30, 40 );
simple_printf("ss", str1.c_str(), str2.c_str() );
return 0 ;
}
使用 可变参数函数 还附带对您可以传递的参数的限制,这在第 5.2.2
节中的 draft C++ standard 函数调用 段落 7:
当给定参数没有参数时,参数的传递方式使得接收函数可以通过调用 va_arg (18.7) 来获取参数的值。对参数表达式执行左值到右值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 标准转换。在这些转换之后,如果参数不具有算术、枚举、指针、指向成员的指针或类类型,则程序是非良构的。如果参数具有非 POD 类类型(第 9 条),则行为未定义。 [...]
您可能不应该这样做,而且您可能可以以更安全、更简单的方式做您想做的事情。从技术上讲,要在 C 中使用可变数量的参数,您需要包含 stdarg.h。从中您将获得 va_list
类型以及对其进行操作的三个函数,称为 va_start()
、va_arg()
和 va_end()
。
#include<stdarg.h>
int maxof(int n_args, ...)
{
va_list ap;
va_start(ap, n_args);
int max = va_arg(ap, int);
for(int i = 2; i <= n_args; i++) {
int a = va_arg(ap, int);
if(a > max) max = a;
}
va_end(ap);
return max;
}
如果你问我,这是一团糟。它看起来很糟糕,不安全,并且充满了与您在概念上试图实现的目标无关的技术细节。相反,请考虑使用重载或继承/多态、构建器模式(如流中的 operator<<()
)或默认参数等。这些都更安全:编译器会更多地了解您正在尝试做的事情,因此有更多的场合它可以在你炸断腿之前阻止你。
...
语法之前提供至少一个参数?
printf()
的情况下,该函数解析字符串参数以获得特殊标记,以计算出它应该在变量参数列表中预期多少额外的参数。
<cstdarg>
而不是 <stdarg.h>
C++17 解决方案:完全类型安全 + 良好的调用语法
由于在 C++11 中引入了可变参数模板并在 C++17 中引入了折叠表达式,因此可以定义一个模板函数,在调用者站点,它可以像可变函数一样被调用,但具有以下优点: :
是强类型安全的;
在没有参数数量的运行时信息或不使用“停止”参数的情况下工作。
这是混合参数类型的示例
template<class... Args>
void print(Args... args)
{
(std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");
另一个对所有参数强制类型匹配:
#include <type_traits> // enable_if, conjuction
template<class Head, class... Tail>
using are_same = std::conjunction<std::is_same<Head, Tail>...>;
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
void print_same_type(Head head, Tail... tail)
{
std::cout << head;
(std::cout << ... << tail) << "\n";
}
print_same_type("2: ", "Hello, ", "World!"); // OK
print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])'
// print_same_type(3, ": ", "Hello, ", "World!");
^
更多信息:
可变参数模板,也称为参数包 Parameter pack(C++11 起) - cppreference.com。折叠表达式折叠表达式(C++17 起) - cppreference.com。请参阅有关 coliru 的完整程序演示。
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
和 Tail...
相同 时才启用此功能”,其中“相同”表示 std::conjunction<std::is_same<Head, Tail>...>
。将最后一个定义读作“Head
与所有 Tail...
相同”。
args
吗?
在 c++11 中,您可以执行以下操作:
void foo(const std::list<std::string> & myArguments) {
//do whatever you want, with all the convenience of lists
}
foo({"arg1","arg2"});
列表初始化 FTW!
在 C++11 中,有一种方法可以创建可变参数模板,这导致了一种非常优雅且类型安全的方式来拥有可变参数函数。 Bjarne 自己在 C++11FAQ 中给出了一个很好的 printf using variable argument templates 示例。
就个人而言,我认为这非常优雅,以至于在编译器支持 C++11 变量参数模板之前,我什至不会在 C++ 中使用变量参数函数。
,
运算符与折叠表达式一起使用)。否则,我不这么认为。
C++ 支持 C 风格的可变参数函数。
然而,大多数 C++ 库使用另一种习惯用法,例如 'c' printf
函数采用可变参数,而 c++ cout
对象使用 <<
重载来解决类型安全和 ADT(可能以实现简单性为代价)。
std::initializer_lists
上传递该列表的结尾......这已经在一个简单的任务中引入了巨大的复杂性。
除了可变参数或重载之外,您还可以考虑将参数聚合到 std::vector 或其他容器(例如 std::map )中。像这样的东西:
template <typename T> void f(std::vector<T> const&);
std::vector<int> my_args;
my_args.push_back(1);
my_args.push_back(2);
f(my_args);
通过这种方式,您将获得类型安全,并且这些可变参数的逻辑含义将是显而易见的。
当然,这种方法可能会出现性能问题,但除非您确定自己无法付出代价,否则您不必担心这些问题。这是一种 c++ 的“Pythonic”方法......
唯一的方法是使用 C 风格的变量参数,如 here 所述。请注意,这不是推荐的做法,因为它不是类型安全且容易出错的。
如果不使用 C 风格的可变参数 (...
),没有标准的 C++ 方法可以做到这一点。
当然,根据上下文,有些默认参数“看起来”像可变数量的参数:
void myfunc( int i = 0, int j = 1, int k = 2 );
// other code...
myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );
所有四个函数调用都使用不同数量的参数调用 myfunc
。如果没有给出,则使用默认参数。但是请注意,您只能省略尾随参数。没有办法,例如省略 i
而只给出 j
。
使用可变参数模板,重现 console.log
的示例,如 JavaScript 中所示:
Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");
文件名,例如 js_console.h
:
#include <iostream>
#include <utility>
class Console {
protected:
template <typename T>
void log_argument(T t) {
std::cout << t << " ";
}
public:
template <typename... Args>
void log(Args&&... args) {
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void warn(Args&&... args) {
cout << "WARNING: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void error(Args&&... args) {
cout << "ERROR: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
};
您可能需要重载或默认参数 - 使用默认参数定义相同的函数:
void doStuff( int a, double termstator = 1.0, bool useFlag = true )
{
// stuff
}
void doStuff( double std_termstator )
{
// assume the user always wants '1' for the a param
return doStuff( 1, std_termstator );
}
这将允许您使用以下四种不同调用之一来调用该方法:
doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );
...或者您可能正在寻找 C 中的 v_args 调用约定。
支持颜色代码的 C++ 11
是通用的,适用于所有数据类型
像 JavaScript console.log(1,"23") 一样工作
支持信息、警告、错误的颜色代码。
例子:
#pragma once
#include <iostream>
#include <string>
const std::string RED = "\e[0;91m";
const std::string BLUE = "\e[0;96m";
const std::string YELLOW = "\e[0;93m";
class Logger {
private:
enum class Severity { INFO, WARN, ERROR };
static void print_colored(const char *log, Severity severity) {
const char *color_code = nullptr;
switch (severity) {
case Severity::INFO:
color_code = BLUE.c_str();
break;
case Severity::WARN:
color_code = YELLOW.c_str();
break;
case Severity::ERROR:
color_code = RED.c_str();
break;
}
std::cout << "\033" << color_code << log << "\033[0m -- ";
}
template <class Args> static void print_args(Args args) {
std::cout << args << " ";
}
public:
template <class... Args> static void info(Args &&...args) {
print_colored("[INFO] ", Severity::INFO);
int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
std::cout << std::endl;
}
template <class... Args> static void warn(Args &&...args) {
print_colored("[WARN] ", Severity::WARN);
int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
std::cout << std::endl;
}
template <class... Args> static void error(Args &&...args) {
print_colored("[ERROR]", Severity::ERROR);
int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
std::cout << std::endl;
}
};
如果你知道将提供的参数数量的范围,你总是可以使用一些函数重载,比如
f(int a)
{int res=a; return res;}
f(int a, int b)
{int res=a+b; return res;}
等等...
正如其他人所说,C 风格的可变参数。但是你也可以用默认参数做类似的事情。
// spawn: allocate and initialize (a simple function)
template<typename T>
T * spawn(size_t n, ...){
T * arr = new T[n];
va_list ap;
va_start(ap, n);
for (size_t i = 0; i < n; i++)
T[i] = va_arg(ap,T);
return arr;
}
用户写道:
auto arr = spawn<float> (3, 0.1,0.2,0.3);
从语义上讲,这看起来和感觉完全像一个 n 参数函数。在引擎盖下,您可能会以一种或另一种方式打开它。
现在可以...使用 boost any 和 templates 在这种情况下,参数类型可以混合
#include <boost/any.hpp>
#include <iostream>
#include <vector>
using boost::any_cast;
template <typename T, typename... Types>
void Alert(T var1,Types... var2)
{
std::vector<boost::any> a( {var1,var2...});
for (int i = 0; i < a.size();i++)
{
if (a[i].type() == typeid(int))
{
std::cout << "int " << boost::any_cast<int> (a[i]) << std::endl;
}
if (a[i].type() == typeid(double))
{
std::cout << "double " << boost::any_cast<double> (a[i]) << std::endl;
}
if (a[i].type() == typeid(const char*))
{
std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
}
// etc
}
}
void main()
{
Alert("something",0,0,0.3);
}
std::any
。
如果所有参数都是 const 且类型相同,我们也可以使用 initializer_list
int fun(int n_args, ...) {
int *p = &n_args;
int s = sizeof(int);
p += s + s - 1;
for(int i = 0; i < n_args; i++) {
printf("A1 %d!\n", *p);
p += 2;
}
}
普通版
typename
vsclass
使用是故意的吗?如果是这样,请解释一下。initializer_list
递归?