Шаблоны Variadic имеют много преимуществ, но есть ли случаи, когда вместо этого следует использовать вариативные функции C-стиля (используя <cstdarg>
)?
Есть ли случай, когда функции vararg предпочтительнее, чем вариативные шаблоны?
Ответ 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