Как использовать вариативные аргументы С++ и 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), я получаю другой путь ошибки парсера, чем когда я их не включаю, хотя оба пути идут к одному и тому же выводу.

Время сообщить комитету, что они что-то пропустили.