Как использовать вариативные аргументы С++ и C вместе?
Как правило, использование функции вариационного шаблона С++ 11 с функциями требует, чтобы аргументы функции на основе вариации были последними в списке аргументов функции. Есть одно исключение; они являются следующими последними аргументами, если существуют вариативные аргументы C-уровня, которые должны быть мертвыми последними.
template < typename ...Args >
int super_printf( Something x, Args &&...a, ... );
Я иногда случайно думаю о С++, и я задавался вопросом, как такая функция может быть реализована. Сначала я подумал об обычном рекурсивном пилинге аргументов из a, затем я вспомнил, что varargs уровня C не каскадируются. Мне нужно сразу же перейти к окончательному va_list.
template < typename ...Args >
int super_vaprintf( Something x, std::va_list &aa, Args &&...a );
// Note that "aa" is passed by reference.
template < typename ...Args >
int super_printf( Something x, Args &&...a, ... )
{
std::va_list args2;
int result;
va_start( args2, XXX ); // (A)
try {
result = super_vaprintf( x, args2, std::forward<Args>(a)... );
} catch ( ... ) {
va_end( args2 ); // (1)
throw;
}
va_end( args2 ); // (2)
return result;
// Can (1) and (2) be compacted with RAII using a custom deleter lambda
// in std::unique_ptr or something? Remember that "va_end" is a macro!
}
Обычный вызов рекурсивного пилинга С++ происходит в вызове super_vaprintf
. В строке (A), что происходит вместо XXX
, "a" или "a..."? Что произойдет, если a пусто, вместо этого x отправляется туда? Если этот последний вопрос верен, мы вкручиваем, если нет x; что нет никаких аргументов, кроме вариационных? (И если это правда, как мы условно кодируем код для использования x, когда a пуст, а в противном случае?)
...
Я просто посмотрел на свою копию стандарта С++ 11 для любой помощи здесь. Кажется, их нет. Это вызовет запрос для комитета С++ вернуться, чтобы исправить это, но я не уверен, что какой-либо способ такой функции можно было бы вызывать без использования всех переменных С++ varargs. Я ошибаюсь; может ли вызов функции использоваться как С++, так и C varargs? Или смешение полезно только для объявлений в терминах трюков с помощью Stupid (Template)?
Ответы
Ответ 1
Когда вы вызываете функцию, последним параметром которой является пакет, все аргументы становятся частью этого пакета. Для va_args
ничего не осталось. Использование явных аргументов шаблона вводит в заблуждение, поскольку они не являются исключительными; они просто предшествуют неявным аргументам.
Чтобы победить вывод, вам нужна ссылка:
(& super_printf<int, int>) ( 0L, 1, 2, 3, 4, 5 )
Это довольно надуманно, но теперь у вас есть проблема ничто не перейти к va_start
.
Чтобы обеспечить разумный интерфейс для пользователей, просто добавьте параметр между двумя списками.
struct va_separator {}; // Empty; ABI may elide allocation.
template < typename ...Args >
int super_printf( Something x, Args &&...a, va_separator, ... );
Для этого super_printf
нужны как явные аргументы для определения пакета, так и явного аргумента разделителя. Но вы можете альтернативно предоставить публичную функцию, которая получает все свои аргументы по пакету, а затем находит разделитель и пересылает в super_printf
, используя явный список аргументов, содержащий элементы пакета перед разделителем.
Ответ 2
Я пробовал этот код на компилирующем веб-сайте (Coliru), с GCC 4.8, и результаты выглядят мрачными. Я не знаю, является ли это GCC в частности, или если все остальные компиляторы там делают что-то подобное. Так могут ли люди с другими компиляторами (Clang, Visual С++, Intel и т.д.) Попробовать?
#include <cstdarg>
#include <iostream>
#include <ostream>
#include <utility>
template < typename ...Args >
int super_vaprintf( long, std::va_list &, Args &&... )
{
return 17;
}
template < typename ...Args >
int super_printf( long x, Args &&...a, ... )
{
std::va_list args2;
int result;
va_start( args2, a ); // (A)
try {
result = super_vaprintf( x, args2, std::forward<Args>(a)... );
} catch ( ... ) {
va_end( args2 );
throw;
}
va_end( args2 );
return result;
}
int main() {
std::cout << super_printf<int, int>( 0L, 1, 2, 3, 4, 5 ) << std::endl; // (B)
return 0;
}
Вызов super_printf
в строке (B) явно устанавливает переменные С++ в две записи int
. Это заставит функцию использовать аргументы 1
и 2
как С++ varargs, а последние три - как var var.
В строке (A) компилятор настаивает на том, что код с a
в нем имеет "...
". Поэтому я меняю его на:
va_start( args2, a... ); // (A)
Я получаю еще одну ошибку о неправильном количестве аргументов. Это имеет смысл, поскольку a
расширяется до двух аргументов. Если я изменяю строку (B) на один С++ vararg:
std::cout << super_printf<int>( 0L, 1, 2, 3, 4, 5 ) << std::endl; // (B)
он работает отлично. Если я полностью удаляю С++ varargs:
std::cout << super_printf<>( 0L, 1, 2, 3, 4, 5 ) << std::endl; // (B)
мы снова получаем ошибку неправильного числа или аргументов, потому что a
имеет длину 0). Если мы сделаем это, когда a
пусто:
va_start( args2, x /*a...*/ ); // (A)
код работает снова, хотя есть предупреждение о x
, не являющемся последним именованным параметром.
Мы можем подойти к примеру по-другому. Пусть reset to:
va_start( args2, a... ); // (A)
//...
std::cout << super_printf( 0L, 1, 2, 3, 4, 5 ) << std::endl; // (B)
где все аргументы после первого сгруппированы как С++ varargs. Конечно, мы получаем ту же ошибку слишком много аргументов в va_start
. Я постепенно комментирую задние аргументы. Он работает, когда осталось ровно два аргумента (что делает a
только один аргумент).
Также существует ошибка, когда остается только один аргумент, но сообщение об ошибке изменяется на явно выраженное "слишком мало" аргументов вместо "неправильной суммы". Как и раньше, я отключил "a...
" для "x
" в строке (A), и код был принят, но предупреждения не было. Поэтому кажется, что когда я явно включаю "<Whatever>
" для super_printf
в строке (B), я получаю другой путь ошибки парсера, чем когда я их не включаю, хотя оба пути идут к одному и тому же выводу.
Время сообщить комитету, что они что-то пропустили.