6

これもまた「誰が上手いの?」のようです。gcc 6.0.0 と clang 3.7.0 の動作が異なるため、質問です。

const char *非テンプレート引数として取り、特定のポインターに特化した変数テンプレートがあるとします。

constexpr char INSTANCE_NAME[]{"FOO"};

struct Struct{ void function() const { std::cout << __PRETTY_FUNCTION__; } };
std::ostream &operator <<(std::ostream &o, const Struct &) { return o << INSTANCE_NAME; }

template <const char *> char   Value[]{"UNKNOWN"};
// spezialization when the pointer is INSTANCE_NAME
template <            > Struct Value<INSTANCE_NAME>{};

テンプレート変数は、特殊化に応じて異なる型を持つことに注意してください。10 には 2 つのテンプレート関数があり、それぞれがconst char *非テンプレート引数として受け取り、それを変数テンプレートに転送します。

template <const char *NAME> void print()
{
    std::cout << Value<NAME> << '\n';
}

template <const char *NAME> void call_function()
{
    Value<NAME>.function();
}

次に、この関数を呼び出すと、異なる動作が発生します。

int main()
{
    print<INSTANCE_NAME>();
    call_function<INSTANCE_NAME>();

    return 0;
}

Code Here

clang 3.7.0 は(私が予想していたように) を出力しFOOvoid Struct::function() constgcc 6.0.0 は以下のエラーでコンパイルに失敗します:

非クラス タイプ 'char [8]' である 'Value' のメンバー 'function' の要求

gcc がテンプレートの非型引数を関数内の変数テンプレートに転送できなかったことはほぼ確実です。このため、型を持つ特殊化されていない変数テンプレートを選択します...NAMEValuecall_function'char [8]'

テンプレート引数をコピーしているように動作しています。これは、オブジェクトのメンバー関数を呼び出した場合にのみ発生します。の本体をコメント化するとcall_function、出力はFOOnotUNKNOWNになるため、print関数内では gcc でも転送が機能します。

そう

  • 正しい動作は何ですか?(mi ベットは clang 用です)
  • 間違ったことをしているコンパイラのバグチケットを開くにはどうすればよいですか?
4

2 に答える 2

5

興味深いのは、この例では GCC が自己矛盾していることです。

不完全なテンプレート クラスを宣言してみましょう。これは、悪用できる素敵なコンパイラ メッセージです。

template <typename T>
struct type_check;

const char*また、テストに使用できる別のものも作成します。

constexpr char NOT_FOO[]{"NOT_FOO"};

次に、コンパイラが何をチョークするかを確認します。

template <const char *NAME> void foo()
{
    type_check<decltype(Value<FOO>)> a;
    type_check<decltype(Value<NAME>)> b;
    type_check<decltype(Value<NOT_FOO>)> c;
    type_check<decltype(Value<FOO>.foo())> d;
    type_check<decltype(Value<NAME>.foo())> e;
    type_check<decltype(Value<NOT_FOO>.foo())> f;
}

GCC 5.1.0 が生成するエラーは次のとおりです (わかりやすくするために少し編集しています)。

test.cpp:21:38: error: ‘type_check<Foo> a’ has incomplete type
     type_check<decltype(Value<FOO>)> a;
                                      ^
test.cpp:22:39: error: ‘type_check<Foo> b’ has incomplete type
     type_check<decltype(Value<NAME>)> b;

test.cpp:25:42: error: ‘type_check<char [8]> c’ has incomplete type
     type_check<decltype(Value<NOT_FOO>)> c;
                                       ^
test.cpp:23:44: error: ‘type_check<void> c’ has incomplete type
     type_check<decltype(Value<FOO>.foo())> c;

test.cpp:24:37: error: request for member ‘foo’ in ‘Value<NAME>’, which is of non-class type ‘char [8]’
     type_check<decltype(Value<NAME>.foo())> d;

test.cpp:28:40: error: request for member ‘foo’ in ‘Value<((const char*)(& NOT_FOO))>’, which is of non-class type ‘char [8]’
     type_check<decltype(Value<NOT_FOO>.foo())> f;

これらを一つ一つ取り上げていきましょう。


エラー 1:

test.cpp:21:38: error: ‘type_check<Foo> a’ has incomplete type
     type_check<decltype(Value<FOO>)> a;

Value<FOO>最初のエラーでは、GCC が の型がisであると正しく推測していることがわかりますFoo。これが私たちの期待です。

エラー 2:

test.cpp:22:39: error: ‘type_check<Foo> b’ has incomplete type
     type_check<decltype(Value<NAME>)> b;

ここで、GCC は転送を正しく行い、Value<NAME>タイプが であることがわかりFooます。

エラー 3:

test.cpp:25:42: error: ‘type_check<char [8]> c’ has incomplete type
     type_check<decltype(Value<NOT_FOO>)> c;

すごいですValue<NOT_FOO>"UNKNOWN"、これで正解です。

エラー 4:

test.cpp:23:44: error: ‘type_check<void> c’ has incomplete type
     type_check<decltype(Value<FOO>.foo())> c;

これは問題ありません。 Value<FOO>isFooを呼び出すことができfoo、 を返しvoidます。

エラー 5:

test.cpp:24:37: error: request for member ‘foo’ in ‘Value<NAME>’, which is of non-class type ‘char [8]’
     type_check<decltype(Value<NAME>.foo())> d;

これは奇妙なものです。Value<NAME>エラー 2 では、GCC が のタイプがであることを認識していることがわかりますFooが、関数のルックアップを試みると、foo間違って取得され、代わりにプライマリ テンプレートが使用されます。これは、非型テンプレート引数の値を正しく解決しない関数ルックアップのバグである可能性があります。

エラー 6:

test.cpp:28:40: error: request for member ‘foo’ in ‘Value<((const char*)(& NOT_FOO))>’, which is of non-class type ‘char [8]’
     type_check<decltype(Value<NOT_FOO>.foo())> f;

ここでは、何が何でValue<NOT_FOO>あるかを判断するときに、コンパイラがプライマリ テンプレートを正しく選択していることがわかります。私が興味を持っているのは、(const char*)(& NOT_FOO))GCC が の型として推測する ですNOT_FOO。多分これは問題へのポインタですか?わからない。


バグを報告し、矛盾を指摘することをお勧めします。質問に対する完全な回答ではないかもしれませんが、お役に立てば幸いです。

于 2015-06-18T08:49:41.700 に答える
3

変数テンプレートの特殊化によって変数テンプレートの型を変更できるという合理的なコンセンサスがあります: C++1y/C++14: 変数テンプレートの特殊化?

のデフォルトの型がメソッドValueを持つ型に変更された場合、gcc の動作は特に興味深いものになります。function

struct Unknown{ void function() const { std::cout << __PRETTY_FUNCTION__; } };
template <const char *> Unknown Value;

prog.cc: In instantiation of 'void call_function() [with const char* NAME = ((const char*)(& INSTANCE_NAME))]':
prog.cc:26:18:   required from here
prog.cc:20:5: error: 'Unknown::function() const' is not a member of 'Struct'
     Value<NAME>.function();
     ^

バグは、特殊化されていない変数テンプレートが変数テンプレート テンプレート パラメーターに依存しない型を持っている場合、gcc はその変数テンプレートを使用するテンプレート メソッド内で、変数テンプレートが常にその型を持っていると見なすようです。

回避策は、通常どおり、変数テンプレートを無条件に、クラス テンプレートの特殊化を伴うクラス テンプレートに転送し、ODR 準拠のために必要な操作を行うことです。

もう 1 つの (おそらくより簡単な) 回避策は、特殊化されていない変数テンプレートの型を変数テンプレートのテンプレート パラメータに何らかの形で依存させることです。あなたの場合、これはうまくいきます:

template <const char *P> decltype(*P)   Value[]{"UNKNOWN"};

gcc bugzillaに対応する問題が見つからないので、新しい問題を入力してください。最小限の例を次に示します。

struct U { void f() {} };
struct V { void f() {} };
template<class T> U t;
template<> V t<int>;
template<class T> void g() { t<T>.f(); }
int main() { g<int>(); }
于 2015-06-18T08:44:02.660 に答える