【发布时间】:2017-03-13 08:39:34
【问题描述】:
可变参数模板有很多优点,但是在某些情况下应该使用 C 风格的可变参数函数(使用<cstdarg>)来代替吗?
【问题讨论】:
标签: c++ c++11 variadic-templates variadic-functions
可变参数模板有很多优点,但是在某些情况下应该使用 C 风格的可变参数函数(使用<cstdarg>)来代替吗?
【问题讨论】:
标签: c++ c++11 variadic-templates variadic-functions
如果您提供具有 C++ 实现的 C API,则模板不是该 API 的选项。可变参数是。
如果您需要支持不支持 C++11 或更新标准的编译器,则不提供可变参数模板。可变参数是。
如果您需要编译防火墙。 IE。您需要从标题中隐藏函数的实现,那么可变参数模板不是一个选项。可变参数是。
在内存受限的系统(嵌入式)上,模板生成的不同功能可能会导致过多的膨胀。也就是说,此类系统通常也是实时的,在这种情况下,由于分支和堆栈的使用,可变参数也可能不可接受。
【讨论】:
我想添加到excellent answer of @user2079303
可变参数也用于某些元编程(例如使用 SFINAE 实现的特征),因为它们在重载决议中被认为是最后一个属性。
假设我们想要实现一个 trait 来检测一个类是否可以从某些类型构造(比如 std::is_constructible:
简化的现代实现将是这样的(这不是唯一的方法:正如所指出的,void_t 也可以 be used to implement the trait 并且很快(2020 年?)我们将不再需要这些噱头,因为 concepts 是以一等公民的身份使用require 子句):
template <class T, class... Args> struct Is_constructible {
template <class... Params>
static auto test(Params... params) -> decltype(T{params...}, std::true_type{});
static auto test(...) -> std::false_type;
static constexpr bool value = decltype(test(std::declval<Args>()...))::value;
};
这是因为SFINAE 起作用:当实例化test 时,如果语义由于某些dependent name 而无效,那么这不是硬错误,而是忽略了重载。
如果您想了解更多关于 trait 技巧及其实现方式和工作原理的信息,您可以进一步阅读:sfinae idiom、member detector idiom、enable_if idiom。
所以,X 类型只能由 2 个整数构造:
struct X { X(int, int) {}; };
我们得到这些结果:
Is_constructible<X, int, int>::value // true
Is_constructible<X, int>::value; // false
现在的问题是我们是否可以用可变参数模板替换可变参数测试:
template <class T, class... Args> struct Is_constructible_broken {
template <class... Params>
static auto test(Params... params) -> decltype(T{params...}, std::true_type{});
template <class... Params>
static auto test(Params...) -> std::false_type;
static constexpr bool value = decltype(test(std::declval<Args>()...))::value;
};
答案是否定的(至少不是直接替代)。当我们实例化
Is_constructible_broken<X, int, int>::value
我们得到一个错误:
错误:重载 '
test(int, int)' 的调用不明确
因为这两个重载都是可行的,并且在重载决议中都具有相同的“等级”。第一个使用可变参数的实现是可行的,因为即使两个重载都是可行的,可变参数模板还是首选可变参数模板。
事实证明,您实际上可以使其与可变参数模板一起使用。诀窍是向test 添加一个人为参数,该参数完美匹配第一个重载并转换为第二个:
struct overload_low_priority{};
struct overload_high_priority : overload_low_priority {};
template <class T, class... Args> struct Is_constructible2 {
template <class... Params>
static auto test(overload_high_priority, Params... params)
-> decltype(T{params...}, std::true_type{});
template <class... Params>
static auto test(overload_low_priority, Params...) -> std::false_type;
static constexpr bool value
= decltype(test(overload_high_priority{}, std::declval<Args>()...))::value;
};
但我认为在这种情况下可变参数更清楚。
【讨论】:
void_t 可能用于创建特征,而不是使用省略号。
struct high_overload_priority : low_overload_priority {}; 使用而不是 int/long 来订购重载。
vararg 允许使用__attribute__ format。例如
void debug(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
void f(float value)
{
debug("value = %d\n", value); // <- will show warning.
}
很遗憾,这不能使用可变参数模板来实现。
编辑:
正如 Vladimir 指出的那样,我忘了提到 __attribute__ format 不是标准的一部分,但是 GCC 和 Clang(但不是 Visual Studio)都支持它。更多详情请见:https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
【讨论】: