13

一般に、関数で C++11 可変個引数テンプレート機能を使用するには、可変個引数ベースの関数引数を関数引数リストの最後にする必要があります。例外が 1 つあります。C レベルの可変引数が存在する場合、それらは最後から 2 番目の引数であり、最後でなければなりません。

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

私はときどき C++ についてランダムに考え、そのような機能をどのように実装できるのか疑問に思いました。最初に、通常の aからの引数の再帰的なピーリングについて考えました。次に、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!
}

通常の C++ 可変長再帰ピーリングがsuper_vaprintf呼び出しで発生します。行 (A) でXXX、「a」または「a...」の代わりに何が入りますか? aが空の場合、代わりにxがそこにある場合はどうなりますか? 最後の質問が真である場合、xがない場合、私たちは台無しになります。可変引数以外に引数がないことを確認しますか? (もしそれが真なら、aが空のときにxを使用しそれ以外の場合に a を使用するようにコードを条件付けするにはどうすればよいでしょうか?)

...

ここでサポートが必要な場合は、C++11 標準のコピーを参照してください。ないようです。これは、C++ 委員会がこれを修正するために戻ってくるように要求するよう促しますが、C++ 可変引数がすべてを取得せずにそのような関数を呼び出す方法があるかどうかはわかりません。私が間違っている; 関数呼び出しで C++ と C の両方の可変引数を使用できますか? それとも、愚かな(テンプレート)インスタンス化のトリックに関して、ミキシングは宣言にのみ役立ちますか?

4

2 に答える 2

0

GCC 4.8 を使用してコンパイル Web サイト (Coliru) でこのコードを試してみましたが、結果は暗いようです。特にGCCなのか、それとも他のすべてのコンパイラが同様のことを行うのかはわかりません。他のコンパイラ (Clang、Visual C++、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;
}

行 (B)への呼び出しsuper_printfは、C++ varargs を 2 つのintエントリに明示的に設定します。これにより、関数は引数12C++ varargs として使用し、後者の 3 つを C varargs として使用します。

行 (A) で、コンパイラは、その中のコードのどこかaに " " があると主張します。...だから私はそれを次のように変更します:

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

引数の数が間違っているという別のエラーが発生します。a2 つの引数に展開されるため、これは理にかなっています。行 (B) を 1 つの C++ vararg に変更すると、次のようになります。

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

それはうまく動作します。C++ varargs を完全に削除すると:

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

a長さがゼロであるため、間違った数または引数エラーが再び発生します)。aが空のときにこれを行うと、次のようになります。

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

x最後に名前が付けられたパラメーターではないという警告がありますが、コードは再び機能します。

別の方法でこの例にアプローチできます。次のようにリセットしましょう。

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

最初の引数以降のすべての引数は、C++ varargs としてグループ化されます。va_startもちろん、引数が多すぎるという同じエラーが発生します。後続の引数を徐々にコメントアウトします。これは、ちょうど 2 つの引数が残っている場合に機能します (make は引数をa1 つだけ持ちます)。

引数が 1 つしか残っていない場合にもエラーが発生しますが、エラー メッセージは「間違った量」ではなく「少なすぎます」という明示的な引数に変わります。a...前と同じように、(A) 行の " " を " "に切り替えたxところ、コードは受け入れられましたが、警告はありませんでした。<Whatever>そのため、行 (B) に" " を明示的に含めるとsuper_printf、それらを含めない場合とは異なるパーサー エラー パスが得られるようですが、両方のパスは同じ結論に達します。

彼らが何かを見落としていたことを委員会に伝える時が来ました....

于 2013-05-16T09:17:09.997 に答える