typedef void(*fn1)(const char *, ...);
typedef std::function<void(const char *, ...)> fn2; // has initializer but incomplete type
直觉上,这些对我来说实际上是一样的,但显然我的直觉让我失望了。我如何协调这些数据类型?
fn2
是一个不完整的类型?fn2
签名需要进行哪些更改,以便让我为它分配一种fn1
?fn2
的lambda时,如何访问可变参数列表?
换句话说,什么是lambda相当于以下?
void fn1_compatible (const char * format, ...) {
va_list args;
va_start(args, format);
//TODO: Do stuff with variadic arguments
va_end(args);
}
注意:另外,这些签名与日志记录有关,但请在一般(非日志记录)上下文中回答问题。
std::function
不支持变量函数。 std::function
采用一种类型,它看起来像这样:
template<class>
class function; // Intentionally incomplete
template<class Ret, class... Args>
class function<Ret(Args...)> {
// Deduce return type and argument types from function type
};
但这并没有推断出可变函数的类型。所以void(const char*)
会工作(Ret
是void
和Args...
是const char*
),但void(const char*, ...)
不起作用(因为这需要从Ret(Args..., ...)
推断)
要从中创建一个仿函数对象,或者只使用一个裸函数指针,就像使用fn1
一样,或者使用vprintf
这样的函数来做C标准库所做的事情:
decltype(auto) my_variadic_function(const char * format, ...) {
va_list args;
va_start(args, format);
try {
auto&& return_value = vmy_variadic_function(format, args);
} catch (...) {
va_end(args);
throw;
}
va_end(args);
return std::forward<decltype(return_value)>(return_value);
}
void vmy_variadic_function(const char* format, va_list args) {
// Do stuff with args
}
然后在vmy_variadic_function
中传递std::function<void(const char*, va_list)>
。
据我所知,你做不到。
如果你可以稍微改变你的前提。那是;而不是使用例如printf
,你可以使用vprintf
。然后你可以:
using fn2 = std::function<int(const char*, va_list)>;
fn2 fun = vprintf;
然后,您可以提供一个包装函数来使用fn2
参数调用...
:
int fun_wrapper(const char *format, ...) {
va_list args;
va_start(args, format);
int ret = fun(format, args);
va_end(args);
return ret;
}
通常...
函数只是包含实际实现的va_list
替代的包装器。就像显示的fun_wrapper
是fun
的包装。
但是如果您要使用的...
函数没有va_list
列表版本,并且没有由您实现,那么您最好的选择可能是使用其他东西。
正如其他人所指出的那样,没有办法将C风格的可变参数列表映射到强类型的C ++函数。虽然可以采用另一种方式,但可以安全地完成这项工作。
在这里,我编写了一个函数forward_to_variadic_fn
,它接受一个C风格的可变参数函数和一个强类型参数列表。变量论证有很多关于正确用法的restrictions,所以我决定实施一些安全检查,在编译时强制执行这些限制。例如,使用此转发功能,当你应该通过std::string
时,你不能不小心通过const char*
// true if T is trivially copy & move constructible and trivially destructible
template<typename T>
constexpr bool trivial_class = (std::is_trivially_copy_constructible_v<T> && std::is_trivially_move_constructible_v<T> && std::is_trivially_destructible_v<T>);
// true if T is acceptable for C-style va_args
template<typename T>
constexpr bool safe_for_va_args = (std::is_null_pointer_v<T> || std::is_pointer_v<T> || std::is_arithmetic_v<T> || std::is_member_pointer_v<T> || trivial_class<T>);
// true if all of Args.. are safe for C-style va_args
template<typename... Args>
constexpr bool all_safe_for_va_args = (true && ... && safe_for_va_args<std::decay_t<Args>>);
template<typename Ret, typename... Args>
Ret forward_to_variadic_fn(Ret(*the_fn)(const char*, ...), const char* format, Args... args){
static_assert(all_safe_for_va_args<Args...>, "The provided types are not safe for use with C-style variadic functions.");
return the_fn(format, args...);
}
int main(){
int n = forward_to_variadic_fn(std::printf, "Hello, %s!\n", "world");
std::cout << n << " characters were written.\n";
std::string mystr = "world!";
// This will compile but is BAD
// std::printf("Hello, %s!\n", mystr);
// The following line will not compile, which is a good thing!
// forward_to_variadic_fn(std::printf, "Hello, %s!\n", mystr);
// This is safe
n = forward_to_variadic_fn(std::printf, "Hello, %s!\n", mystr.c_str());
std::cout << n << " characters were written.\n";
return 0;
}
当然,这不能避免使用不正确的格式化标志,但它可以避免许多其他未定义的行为。
编辑以供解释:帮助模板变量all_safe_for_va_args
用于强制对可变参数函数的参数进行限制,如cppreference所述:
当调用可变参数函数时,在左值到右值,数组到指针和函数到指针转换之后,作为变量参数列表一部分的每个参数都会经历额外的转换,称为默认参数提升:
std::nullptr_t
被转换为void*
float
参数在浮点数促销中转换为double
bool
,char
,short
和unscoped枚举转换为int
或更宽的整数类型,如整数提升只允许使用算术,枚举,指针,成员指针和类类型参数(具有非平凡复制构造函数,非平凡移动构造函数或非平凡析构函数的类类型除外,它们通过实现定义有条件地支持语义)
<type_traits>
库中的许多辅助性状类和辅助模板变量巧妙地捕获了这些单独的条件。例如,std::is_null_pointer_v<T>
是一个编译时常量,当且仅当true
是T
时才会计算为nullptr_t
。
变量模板safe_for_va_args<T>
详尽地检查这些要求,因此只要类型T
满足上述条件就是如此。为了为接受任意数量类型的相同条件创建变量模板,我使用fold expression有效地对参数包扩展进行逻辑AND,这可以在all_safe_for_va_args<T>
的实现中看到。
例如,all_safe_for_va_args<int, nullptr, const char*>
评估为:
(true && safe_for_va_args<int> && safe_for_va_args<nullptr> && safe_for_va_args<const char*>)
所有这些都是true
,所以整个表达都是真的。
这与static_assert
很好地结合,std::string
在编译时检查自定义条件,并且可以提供用户友好的错误消息,而不是模板替换失败的神秘链。
通常,允许传递给C风格的可变参数函数的类型是可以逐位复制的类型,不需要任何特殊维护。 const char*
失败了,因为它必须执行内存分配和解除分配,因此既没有简单的构造函数也没有析构函数。另一方面,int
或auto thing = [](const char * format, ...) {
va_list args;
va_start(args, format);
//TODO: Do stuff with variadic arguments
va_end(args);
};
可以安全地逐位复制,并且在逻辑上相同。
varargs的lambda形式是独立函数形式的直接转换。
__builtin__vastart
这被G ++接受。
不幸的是,clang似乎有一个错误,它无法识别其qazxswpoi关键字是在具有可变参数列表的lambda中使用的。