2013-05-15 13 views
11

Generalnie, użycie funkcji szablonu variasic C++ 11 z funkcjami wymaga, aby argumenty funkcji oparte na variadic były ostatnimi na liście argumentów funkcji. Jest jeden wyjątek; są one argumentami następnymi, jeśli istnieją argumenty wariacyjne na poziomie C, które muszą być martwe jako ostatnie.W jaki sposób argumenty wariancji C++ i C mogą być używane razem?

template < typename ...Args > 
int super_printf(Something x, Args &&...a, ...); 

czasami losowo myśleć o C++, i zastanawiałem się, jak taka funkcja może być realizowana. Najpierw myślałem o zwykłym rekurencyjnym obieraniu argumentów z a, potem przypomniałem sobie, że warianty poziomu C nie kaskadają. Muszę od razu zamienić je na ostateczną listę 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! 
} 

Zwykła C++ o zmiennej liczbie argumentów rekurencyjne złuszczanie się dzieje w zaproszeniu super_vaprintf. W linii (A), co dzieje się na miejscu XXX, "a" lub "a ..."? Co się stanie, jeśli abędzie puste, czy zamiast tego będzie tam x? Jeśli to ostatnie pytanie jest prawdziwe, czy wkręcamy je, jeśli nie ma żadnego x; że poza tymi wariantowymi nie ma żadnych argumentów? (A jeśli to prawda, w jaki sposób conditionalize kod używać x gdy jest pusta, a się inaczej?)

...

Właśnie spojrzałem na kopię Standard C++ 11 dla każdej pomocy tutaj. Wygląda na to, że nie ma żadnych. Spowodowałoby to żądanie, aby komitet C++ powrócił, aby to naprawić, ale nie jestem pewien, czy istnieje sposób, aby można było wywołać taką funkcję, gdyby nie wszystkie warianty C++. Czy się mylę; czy można wywołać funkcję, aby używać zarówno C++ jak i C varargs? A może mieszanie jest użyteczne tylko w przypadku deklaracji, jeśli chodzi o głupie sztuczki (szablon)?

+0

Zaraz po wysłaniu tego, zdałem sobie sprawę, że może coś w stylu "' super_printf (Something {}, 1, 2, 3, 4) '" może wymusić pewne argumenty jako C-level ("3" i "4" w tym przypadku). – CTMacUser

+1

są one ortogonalne, ponieważ c va działa w czasie wykonywania, a szablony variadic C++ pracują w czasie kompilacji –

+0

@CTMacUser: tak, to by działało. Pytanie pozostaje takie, jak jest użyteczne, i powiedziałbym w metaprogramowaniu, że może to być . może uda ci się znaleźć lepszy wgląd w czytanie propozycji szablonów variadic, mogą wyjaśnić, dlaczego jest to możliwe. – PlasmaHH

Odpowiedz

0

Próbowałem tego kodu na kompilowanej stronie internetowej (Coliru), z GCC 4.8, a wyniki wyglądają ponuro. Nie wiem, czy to w szczególności GCC, czy też wszystkie pozostałe kompilatory robią coś podobnego. Czy ludzie z innymi kompilatorami (Clang, Visual C++, Intel itp.) Mogą to wypróbować?

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

Wezwanie do super_printf na linii (B) wyraźnie określa C++ varargs dwóch int wpisów. To sprawi, że funkcja użyje argumentów jako warianty C++, a ostatnie trzy jako C varargs.

W linii (A) kompilator nalega, aby kod z a w nim miał kod "...". Więc zmieniam to na:

va_start(args2, a...); // (A) 

Dostaję kolejny błąd związany z nieprawidłową liczbą argumentów. Ma to sens, ponieważ a rozwija się do dwóch argumentów. Jeśli zmienię linię (B) na jedną wersję C++:

std::cout << super_printf<int>(0L, 1, 2, 3, 4, 5) << std::endl; // (B) 

działa dobrze. Jeśli usunąć C++ varargs całości:

std::cout << super_printf<>(0L, 1, 2, 3, 4, 5) << std::endl; // (B) 

otrzymujemy błąd Wrong-number-or-argumenty ponownie, ponieważ a ma długość zero).Jeśli to zrobimy, gdy a jest pusty:

va_start(args2, x /*a...*/); // (A) 

kod działa ponownie, chociaż pojawia się ostrzeżenie o x nie będąc ostatni nazwany parametr.

Możemy podejść do przykładu w inny sposób. Zresetujmy do:

va_start(args2, a...); // (A) 
//... 
std::cout << super_printf(0L, 1, 2, 3, 4, 5) << std::endl; // (B) 

gdzie wszystkie argumenty po pierwszym są zgrupowane jako warianty C++. Oczywiście otrzymujemy ten sam zbyt wiele-argumencyjny błąd w va_start. Stopniowo komentuję końcowe argumenty. Działa, gdy pozostały dokładnie dwa argumenty (co powoduje, że a ma dokładnie jeden argument).

Występuje również błąd, gdy pozostaje tylko jeden argument, ale komunikat o błędzie zmienia się, aby jawnie powiedzieć "zbyt mało" zamiast "niewłaściwej ilości". Tak jak poprzednio, wyłączyłem "a..." dla "x" w linii (A), a kod został zaakceptowany, ale nie było ostrzeżenia. Tak więc wydaje się, że gdy jawnie włączam "<Whatever>" dla super_printf w linii (B), otrzymuję inną ścieżkę błędu analizatora składni niż wtedy, gdy ich nie uwzględniam, chociaż obie ścieżki idą do tego samego wniosku.

Czas powiedzieć komisję, że coś przeoczyłem ....

+0

Odnośnie ostatniego akapitu ... nie, nie bardzo. –

+0

@LightnessRacesinOrbit, faktycznie [tak] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1790). – CTMacUser

+0

: P BTW Coliru obsługuje Clang –

4

Po wywołaniu funkcji, której ostatni parametr jest paczka, wszystkie argumenty stają się częścią tego pakietu. Nie ma już nic dla va_args. Używanie jawnych argumentów z szablonu jest mylące, ponieważ nie są one wyłączne; poprzedzają one implicite argumenty.

Aby pokonać odliczenia, trzeba odniesienie:

(& super_printf<int, int>) (0L, 1, 2, 3, 4, 5) 

Jest to dość wymyślony, ale teraz masz problem z niczym, aby przejść do va_start.

Aby zapewnić rozsądny interfejs dla użytkowników, wystarczy dodać parametr między tymi dwoma listami.

struct va_separator {}; // Empty; ABI may elide allocation. 

template < typename ...Args > 
int super_printf(Something x, Args &&...a, va_separator, ...); 

Ten super_printf będą musiały zarówno wyraźnych argumentów zdefiniować pakiet i wyraźny separatora argument. Można jednak alternatywnie podać funkcję publiczną, która odbiera wszystkie argumenty według pakietu, a następnie odnajduje separator i przekazuje do super_printf przy użyciu jawnej listy argumentów zawierającej elementy pakietu przed separatorem.