4

可変引数とその後のいくつかの固定引数を持つ関数ポインターを受け取る関数を作成する必要があり、Visual Studio 2013 で機能させることができませんでした。必要なことを実行し、gcc と clang に対して試した例です。そして、3 つのコンパイラすべてでまったく異なる結果が得られました。したがって、私が解決したい質問は次のとおりです。

  1. 私の例はまったく有効ですか?そうでない場合、私は何を間違っていますか?
  2. 私の例が有効な場合、gcc と clang の動作に関するヒントはありますか (これはブラック ボックスであるため、msvc を数えましょう)。

例:

#include <iostream>

struct foo
{
    void work(int first, int second, int third)
    {
        std::cout << "0: " << first << ",1: " << second << ",2: " << third << std::endl;
    }
    void work_with_double(double first, int second, int third, int fourth)
    {
        std::cout << "0: " << first << ",1: " << second << ",2: " << third << ",3: " << fourth << std::endl;
    }
};

template<typename ... argument_types>
void invoke_foo(foo* instance, int first, int second, int third, void (foo::*method)(argument_types ... arguments, int, int, int), argument_types ... arguments)
{
    (instance->*method)(arguments ..., first, second, third);
}

int main(int argc, char** argv)
{
    foo instance;
    invoke_foo(&instance, 1, 2, 3, &foo::work); // gcc ok, clang err, msvc 2013 err
    invoke_foo<>(&instance, 1, 2, 3, &foo::work); // gcc ok, clang err, msvc 2013 err
    invoke_foo(&instance, 1, 2, 3, &foo::work_with_double, 1.0); // gcc err, clang ok, msvc 2013 err
    invoke_foo<double>(&instance, 1, 2, 3, &foo::work_with_double, 1.0); // gcc err, clang err, msvc 2013 ok
    return 0;
}

Visual Studio 2015 (更新なし) をクラッシュさせる変更されたスニペット

をオブジェクトのメンバー関数invoke_fooとして作成すると、Visual Studio 2015 がクラッシュします。

#include <iostream>
#include <memory>

struct foo
{
    void work(int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eight)
    {
        std::cout << "0: " << first << ",1: " << second << ",2: " << third << std::endl;
    }
    void work_with_double(double firstExtra, int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eight)
    {
        std::cout << "0: " << first << ",1: " << second << ",2: " << third << ",3: " << fourth << std::endl;
    }
};

struct bar
{

};

struct wrapper
{

    template <typename T> struct non_deduced { using type = T; };
    template <typename T> using non_deduced_t = typename non_deduced<T>::type;

    template<typename ... argument_types>
    std::shared_ptr<bar> invoke_foo(int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eight, void (foo::*method)(non_deduced_t<argument_types>... arguments, int, int, int, int, int, int, int, int), argument_types ... arguments)
    {
        (foo_.get()->*method)(arguments ..., first, second, third, fourth, fifth, sixth, seventh, eight);
        return nullptr;
    }

    std::unique_ptr<foo> foo_ = std::move(std::unique_ptr<foo>(new foo));

};

int main(int argc, char** argv)
{
    wrapper instance;
    instance.invoke_foo(1, 2, 3, 4, 5, 6, 7, 8, &foo::work);
    instance.invoke_foo(1, 2, 3, 4, 5, 6, 7, 8, &foo::work_with_double, 1.0);
}
4

1 に答える 1

4

それぞれの場合の問題は、コンパイラが引数から推論しようとしていることargument_typesですmethod。可変個引数のテンプレート パラメーターは、引数リストの最後にある場合にのみ推論できるため、これは違法です。

void (foo::*method)(argument_types ... arguments, int, int, int)
                    ^^^^^^^^^^^^^^^^^^ can't infer here
                                                ^^^^^^^^^^^^^^^ because of these

argument_types回避策は、次のようなヘルパーを使用して、このコンテキストで推測されないように保護することidentityです。

template<class T> struct identity { using type = T; };
template<class T> using identity_t = typename identity<T>::type;

// ...

template<typename ... argument_types>
void invoke_foo(foo* instance, int first, int second, int third,
    void (foo::*method)(identity_t<argument_types> ... arguments, int, int, int), argument_types ... arguments)
//                      ^^^^^^^^^^^ fix here

これはコードのバグですか、それともコンパイラのバグですか? 実際、これはコンパイラのバグです (はい、すべてです)。問題は、引数リストの最後ではなく、関数型内に表示されるパラメーター パックが非推定コンテキストであるかどうかです。標準の関連部分は[temp.deduct.type]で、次のように述べています。

5 - 非推定コンテキストは次のとおりです: [...]

  • parameter-declaration-list の末尾にない関数パラメーター パック。

6 - 非推定コンテキストを含む方法でタイプ名が指定されている場合、そのタイプ名を構成するすべてのタイプも非推定です。ただし、複合型には、推定型と非推定型の両方を含めることができます。

ここで、argument_typesは の型を推定するときは非推定コンテキストmethodですが、 の末尾の引数の型を推定するときは推定コンテキストですinvoke_foo

テストできる別のコンパイラは、ICC (Intel C++ Compiler) です。ICC は最初の 2 つの形式を拒否し、最後の 2 つの形式を受け入れます。gcc とは正反対です。コンパイラの動作が大きく異なる理由は、この種のコードを扱うことは本質的にエラー処理の問題であり、具体的にはテンプレート パラメーターが推定されないコンテキストに現れるときを認識し、代わりに他の場所で推定される型を使用するためです。コンパイラは (独自の方法で)argument_types内で推論できないことを認識していますmethodが、他の場所で推論できることを認識または受け入れていません。

具体的には、次のようになります。

  • argument_typesgcc は、から推測できない場合method、空でなければならないと想定します。
  • clang は、argument_typesが空であるか明示的に指定されていると推定される場合、これはエラーであるに違いないと想定します。
  • MSVC は、argument_typesオーバーライドの推定に失敗を推定させることはできませんが、明示的に指定されている場合は問題ありません。
  • argument_typesICC は、が空であると推定される場合、これはエラーであるに違いないと想定します。
于 2016-06-22T14:22:57.570 に答える