Есть ли случай, когда функции vararg предпочтительнее, чем вариативные шаблоны?

Шаблоны Variadic имеют много преимуществ, но есть ли случаи, когда вместо этого следует использовать вариативные функции C-стиля (используя <cstdarg>)?

Ответы

Ответ 1

  • Если вы предоставляете C API с реализацией С++, тогда шаблоны не являются опцией для API. Варгары.

  • Если вам нужно поддерживать компилятор, который не поддерживает стандарт С++ 11 или более новый, то вариативные шаблоны недоступны. Варгары.

  • Если вам нужен компиляторный брандмауэр. То есть вам нужно скрыть реализацию функции из заголовка, тогда вариационный шаблон не является вариантом. Варгары.

  • В системах с ограниченной памятью (вложенных) различные функции, генерируемые шаблоном, могут вводить слишком много раздува. Тем не менее, такие системы обычно также являются реальным временем, и в этом случае varargs также могут быть неприемлемыми из-за разветвления и использования стека.

Ответ 2

Я хочу добавить к отличный ответ @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;
};

Это работает из-за SFINAE: при создании экземпляра test, если семантика недействительна из-за некоторого зависимое имя, то это не является жесткой ошибкой, вместо этого перегрузка просто игнорируется.

Если вы хотите узнать больше о трюках с трюками и о том, как они реализованы и как они работают, вы можете прочитать далее: sfinae idiom, идентификатор идентификатора участника, enable_if idiom.

Итак, с типом X, который может быть построен только из 2 ints:

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

получаем следующие результаты:

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

Теперь возникает вопрос, можем ли мы заменить тест 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;
};

И ответ - нет (по крайней мере, не прямая замена). Когда мы создаем экземпляр

Is_constructible_broken<X, int, int>::value

получаем ошибку:

ошибка: вызов перегруженного '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;
};

Но я думаю, что varargs более ясен в этом случае.

Ответ 3

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

К сожалению, этого не может быть достигнуто с использованием вариативных шаблонов.

Отредактировано: Как заметил Владимир, я забыл упомянуть, что __attribute__ format не входит в стандарт, однако он поддерживается как GCC, так и Clang (но не Visual Studio). Для получения дополнительной информации см.: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes