是否存在vararg函数优先于可变参数模板的情况?

Vla*_*iev 19 c++ variadic-functions variadic-templates c++11

可变参数模板有很多优点,但有时候<cstdarg>应该使用C风格的可变参数函数(使用)吗?

eer*_*ika 25

  1. 如果您提供带有C++实现的C API,则模板不是API的选项.Varargs是.

  2. 如果需要支持不支持C++ 11或更新标准的编译器,则可变参数模板不可用.Varargs是.

  3. 如果需要编译防火墙.即你需要从头部隐藏函数的实现,然后variadic模板不是一个选项.Varargs是.

  4. 在内存受限系统(嵌入式)上,模板生成的不同函数可能会引入太多膨胀.也就是说,这样的系统通常也是实时的,在这种情况下,由于分支和堆栈使用,varargs也可能是不可接受的.

  • varargs也用于一些元编程(例如用SFINAE实现的特性),因为它们在重载决策上被认为是最后的属性. (11认同)

bol*_*lov 14

我想补充@ user2079303优秀答案

varargs也用于一些元编程(例如用SFINAE实现的特性),因为它们在重载决策上被认为是最后的属性.

假设我们想要实现一个特征来检测一个类是否可以从某些类型构造(类似于std :: is_constructible:

一个简化的现代实现将是这样的(它不是唯一的方式:正如所指出的,void_t可用于实现特性,很快(2020?)我们将不需要任何这些噱头,因为概念正在与他们的方式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;
};
Run Code Online (Sandbox Code Playgroud)

这是因为SFINAE:在实例化时test,如果语义由于某些依赖名称而无效,那么这不是硬错误,而是忽略了重载.

如果你想进一步了解traits技巧及其实现方式和工作原理,你可以进一步阅读:sfinae idiom,成员探测器成语,enable_if idiom.

因此,对于X只能从2个整数构造的类型:

struct X { X(int, int) {}; };
Run Code Online (Sandbox Code Playgroud)

我们得到这些结果:

Is_constructible<X, int, int>::value // true
Is_constructible<X, int>::value;     // false
Run Code Online (Sandbox Code Playgroud)

现在的问题是我们是否可以使用可变参数模板替换varargs测试:

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;
};
Run Code Online (Sandbox Code Playgroud)

答案是否定的(至少不是直接替代).当我们实例化时

Is_constructible_broken<X, int, int>::value
Run Code Online (Sandbox Code Playgroud)

我们收到一个错误:

错误:重载' test(int, int)'的调用是不明确的

因为两个重载都是可行的,并且在重载决策中都具有相同的"等级".使用varargs的第一个实现是有效的,因为即使两个重载都可行,可变参数模板也优于vararg模板.

事实证明,您实际上可以使用可变参数模板.诀窍是添加一个人工参数,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;
};
Run Code Online (Sandbox Code Playgroud)

但我认为在这种情况下varargs更清楚.


Kub*_* S. 5

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.
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,这无法使用可变参数模板来实现。

编辑:正如 Vladimir 指出的,我忘了提及这__attribute__ format不是标准的一部分,但 GCC 和 Clang 都支持它(但 Visual Studio 不支持)。有关更多详细信息,请参阅:https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes