22

Variadic templates have lot of advantages, but are there occasions when C-style variadic functions (using <cstdarg>) should be used instead?

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Vladimir Yanakiev
  • 1,240
  • 1
  • 16
  • 25

3 Answers3

29
  1. If you provide a C API with C++ implementation, then templates are not an option for the API. Varargs are.

  2. If you need to support a compiler that doesn't support C++11 or newer standard, then variadic templates are not available. Varargs are.

  3. If you need a compilation firewall. I.e. you need to hide the implementation of the function from the header, then variadic template is not an option. Varargs are.

  4. On memory constrained systems (embedded), the different functions generated by the template may introduce too much bloat. That said, such systems are typically also real time, in which case varargs might also unacceptable due to branching and stack usage.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 13
    varargs are also used in some metaprogramming (traits implemented with SFINAE for instance) because of their property of being considered last on the overload resolution. – bolov Oct 31 '16 at 09:46
  • @bolov cool! I've never encountered that use case. Sounds a bit *too* clever for my good though :) – eerorika Oct 31 '16 at 09:59
  • @bolov would you mind to give more details, even code example or link – Vladimir Yanakiev Oct 31 '16 at 10:03
  • @user2079303 I've added an example if you are interested. – bolov Oct 31 '16 at 10:37
  • @bolov one possible caveat with this usage though: it might be harder for a compiler to optimize varargs functions. At least MSVC2015 has difficulties optimizing such code - it didn't inline trivial functions, didn't propagate known values through call etc. Using different rules for overload resolution may be beneficial to performance. I used pointer vs bool (when called with pointer, bool version ranks lower than pointer version), and the compiler was able to do amazing job collapsing complicated call-chain into single optimized function. – Andrey Turkin Nov 02 '16 at 12:09
  • @AndreyTurkin see my answer. The usage I refer to doesn't involve function body, just declaration to be used in the type system. So there is absolutely no performance difference simply because the function is never called (it doesn't even compile to code because it doesn't have a body). That being said about my anser, it is absolutely true that a vararg function is nearly impossible to optimize simply because the parameters are not known at compile time. As for `int` vs `bool`, instead of that I suggest using structs like what I show in my answer to prioritize overloads. – bolov Nov 02 '16 at 12:16
  • Nice use case with (3), but I guess it'd still be best to hide your varargs based implementation and expose a thin variadic wrapper to provide a type-safe and convenient interface. – enobayram Nov 06 '16 at 09:44
16

I want to add to the excellent answer of @user2079303

varargs are also used in some metaprogramming (traits implemented with SFINAE for instance) because of their property of being considered last on the overload resolution.

Let's say we want to implement a trait to detect if a class is constructible from some types (something like std::is_constructible:

A simplified modern implementation would go like this (it's not the only way: as pointed out, void_t can also be used to implement the trait and soon (2020?) we won't need any of these gimmicks as concepts are on their way with the require clause as a first-class citizen):

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

This works because of SFINAE: when instantiating test, if the semantics are invalid due to some dependent name, then that isn't a hard error, instead that overload is just ignored.

If you want to know more about traits tricks and how they are implemented and how they work, you can read further: sfinae idiom, member detector idiom, enable_if idiom.

So, with a type X that can be constructed from 2 ints only:

struct X { X(int, int) {}; };

we get these results:

Is_constructible<X, int, int>::value // true
Is_constructible<X, int>::value;     // false

The question now is whether we can replace the varargs test with variadic templates:

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

And the answer is no (at least not a direct replacement). When we instantiate

Is_constructible_broken<X, int, int>::value

we get an error:

error: call of overloaded 'test(int, int)' is ambiguous

because both overloads are viable and both have the same "rank" in overload resolution. The first implementation with varargs works because even if both overloads are viable, the variadic template one is preferred over the vararg one.

It turns out that you can actually make it work with variadic templates. The trick is to add an artificial parameter to test that's a perfect match for the first overload and a conversion for the second:

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

But I think the varargs is more clear in this case.

Community
  • 1
  • 1
bolov
  • 72,283
  • 15
  • 145
  • 224
4

vararg allows using __attribute__ format. E.g.

void debug(const char *fmt, ...) __attribute__((format(printf, 1, 2)));

void f(float value)
{
  debug("value = %d\n", value); // <- will show warning.
}

Unfortunately this can't be achieved using variadic templates.

Edited: As Vladimir pointed out, I forgot to mention that __attribute__ format is not part of the standard, however it is supported by both GCC and Clang (but not Visual Studio). For more details please see: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

Kuba S.
  • 186
  • 2
  • 4
  • I would suggeset you to put link of the usage of __attribute__ format as it is not in standard. I also think that it is good to show some of the compilers it is used. I know only GCC. – Vladimir Yanakiev Nov 07 '16 at 13:56