1

そのため、オンラインのmsコンパイラはこれをコンパイルできませんでした(私の家のVS2012とSP1(+ 11月パック)のように)。clangと最新のgccはコンパイルできました。VS に欠けている C++11 の機能を教えてください。回避策はありますか?

#include <iostream>
#include <utility>
#include <type_traits>

struct A {
    int x;

    void a() {
        std::cout << "an a! " << x << "\n";
    }
};

struct B {
    double x;

    double b(double k) {
        std::cout << "b! " << x << ", " << k << "\n";
        return x - k;
    }

    void b() {
        std::cout << "b! " << x << ", ?\n";
    }
};

struct C {
    A *_first__;
    B *_second__;
     C(A * _first__, B * _second__):_first__(_first__), _second__(_second__) {
    } template < typename K, typename ... T > static auto _a_caller__(K * k, T && ... args)->decltype(k->a(std::forward < T > (args) ...)) {
    return k->a(std::forward < T > (args)...);
    }
    template < typename...T > auto a(T &&...args)->decltype(_a_caller__(_first__, std::forward < T > (args)...)) {
        return _a_caller__(_first__, std::forward < T > (args)...);
    }
    template < typename...T > auto a(T &&...args)->decltype(_a_caller__(_second__, std::forward < T > (args)...)) {
        return _a_caller__(_second__, std::forward < T > (args)...);
    }
    template < typename K, typename...T > static auto _b_caller__(K * k, T && ... args)->decltype(k->b(std::forward < T > (args) ...)) {
        return k->b(std::forward < T > (args)...);
    }
    template < typename...T > auto b(T &&...args)->decltype(_b_caller__(_first__, std::forward < T > (args)...)) {
        return _b_caller__(_first__, std::forward < T > (args)...);
    }
    template < typename...T > auto b(T &&...args)->decltype(_b_caller__(_second__, std::forward < T > (args)...)) {
        return _b_caller__(_second__, std::forward < T > (args)...);
    }
};

int main() {
    A a {12};
    B b {24};

    C c (&a, &b);

    c.a();
    c.b();
    std::cout << c.b(2445) << std::endl;
}

エラー:

testvc.cpp
--\testvc.cpp(38) : error C2535: 'unknown-type C::a(T &&...)' : member function already defined or declared
        --\testvc.cpp(33) : see declaration of 'C::a'
--\testvc.cpp(47) : error C2535: 'unknown-type C::b(T &&...)' : member function already defined or declared
        --\testvc.cpp(42) : see declaration of 'C::b'
--\testvc.cpp(56) : error C2893: Failed to specialize function template 'unknown-type C::a(T &&...)'
        With the following template arguments:
        ''
--\testvc.cpp(57) : error C2893: Failed to specialize function template 'unknown-type C::b(T &&...)'
        With the following template arguments:
        ''
--\testvc.cpp(58) : error C2893: Failed to specialize function template 'unknown-type C::b(T &&...)'
        With the following template arguments:
        'int'
4

2 に答える 2

3

[この回答は更新されました。テキストの最後にある編集を参照してください]

これをSSCCEに落とし込みました:

#include <iostream>

struct A { A g(int) { return A(); } };
struct B { B g() { return B(); } };

struct C
{
    template<typename... Ts>
    auto f(Ts... ts) -> decltype(A().g(ts...)) 
    { std::cout << "f -> A" << std::endl; return A(); }

    template<typename... Ts>
    auto f(Ts... ts) -> decltype(B().g(ts...)) 
    { std::cout << "f -> B" << std::endl; return B(); }
};

int main()
{
    C c;
    c.f(1);
}

GCC 4.7.2 と Clang 3.2 はこれをコンパイルしますが、VC11 はコンパイルしません。実際、VC11 は、式の中で置換が失敗した場合に SFINAE を適用しないようで、これはバグdecltypeである可能性が最も高いです。

実際、C++11 標準では (14.8.2/7) が指定されています。

置換は、関数の型とテンプレート パラメーターの宣言で使用されるすべての型と式で発生します。式には、配列境界内または非型テンプレート引数として現れるような定数式だけでなく、 、、および非定数式を許可するその他のコンテキスト内の一般式 (つまり、非定数式)も含まれます。sizeofdecltype[...]

また、SFINAE に関連するのは 14.8.2/8 で、以下が追加されます。

置換の結果が無効な型または式になる場合、型推定は失敗します。無効な型または式は、置換された引数を使用して記述した場合に不適切な形式になるものです。[...]関数型とそのテンプレート パラメーター型の直接のコンテキストで無効な型と式のみが、推論の失敗につながる可能性があります。

では、これは「直接的な文脈での」置換失敗のケースですか? 同じ段落で、「即時コンテキスト」の意味を明確にしています。

注:置換された型および式の評価は、クラス テンプレートの特殊化および/または関数テンプレートの特殊化のインスタンス化、暗黙的に定義された関数の生成などの副作用をもたらす可能性があります。 」であり、プログラムの形式が正しくない可能性があります。

私の単純化された例の場合、テンプレートのインスタンス化や特殊化がまったく含まれていないため、置換の失敗は間違いなく即時のコンテキストで発生します。したがって、VC11 には間違いなくバグが含まれています。

ただし、式内で関数テンプレート()のインスタンス化が試行されるため例で置換が「即時コンテキスト」で発生するかどうかはあまり明確ではありません。decltype_b_caller__

ここでの重要な観察結果は、インスタンス化が試行されたが実行されなかったということです。これは、型推定decltypeが失敗したためです (インスタンス化が試行されたテンプレート関数の句の式の代入の失敗が原因で)。したがって、テンプレートのインスタンス化のネストされたコンテキストではエラーは発生しません。

したがって、これはVC11 バグとして認定されます。

PS:ネストされたコンテキストで置換エラーが発生するために SFINAE が適用されない状況については、SO に関するこの Q&A を参照してください。

編集:

私の答えは間違っていたことが判明しましたが、その理由は自明ではなく、一部の人にとって役立つ可能性があると考えているため、元のテキストを保持することにしました. しかし、私は 1 つの重要な側面を見落としていました。

Johannes Schaubが以下のコメントで正しく指摘したように、上記の 2 番目の定義は形式f()が正しくなく、診断は必要ありません。これは、C++11 標準のパラグラフ 14.6/8 で規定されています。

[...] 可変個引数テンプレートのすべての有効な特殊化で空のテンプレート パラメーター パックが必要な場合、テンプレート定義の形式が正しくなく、診断は不要です。[...]

したがって、プログラムの形式が正しくなくても、コンパイラは (許可されていても) エラーを発行する必要はありません。これは、このプログラムのコンパイルの失敗は VC11のバグではなく、コンパイラが検出する必要のないエラーを検出するという点で、むしろ優れた機能であることを意味します (ただし、エラー メッセージはかなり誤解を招くものであると言わざるを得ません)。

于 2013-02-01T15:39:54.430 に答える
1

それは複雑です。GCC と CLANG は SFINAE を使用して 2 つの関数テンプレートのあいまいさを解消していると思いますが、一見あいまいです。例を見てみましょう: 呼び出しc.b(2445)で、型と実際の引数を少し混乱させますが、私の言いたいことが理解できることを願っています。

  1. 最初の関数テンプレートのインスタンス化、つまり

    auto b<int>(int args)->decltype(_b_caller__(_first__, std::forward <int> (args)))をインスタンス化_b_caller<A,int>し、 を呼び出します_first__->b(int)。A にはメソッド b がないため、両方のインスタンス化が失敗します。これはにつながります

  2. 2 番目の関数テンプレートのインスタンス化、つまり

    auto b<int>(int args)->decltype(_b_caller__(_second__, std::forward <int> (args)))_b_caller<B,int>Bにはメソッドb(double)があるため、これは機能します。

そのプロセスのどこかで Visual Studio が救済されたようです。私の推測では、SFINAE は後続の戻り値の型では正しく機能しませんが、その場合に SFINAE を正しく適用するのが難しくなるのは、2 レベルの深いインスタンス化である可能性もあります。

編集:これは関連している可能性があります: SFINAE がこれに適用されないのはなぜですか?

于 2013-02-01T15:12:18.917 に答える