web-dev-qa-db-ja.com

エイリアステンプレートが競合する宣言を行うのはなぜですか?

Clangからg ++へのいくつかのC++ 11コードの移植

template<class T>
using value_t = typename T::value_type;

template<class>
struct S
{
    using value_type = int;
    static value_type const C = 0;
};

template<class T> 
value_t<S<T>> // gcc error, typename S<T>::value_type does work
const S<T>::C;

int main() 
{    
    static_assert(S<int>::C == 0, "");
}

clang(バージョン3.1からSVNトランク)とg ++バージョンで異なる動作をします。後者の場合、エラーが発生します このように

prog.cc:13:13: error: conflicting declaration 'value_t<S<T> > S< <template-parameter-1-1> >::C'
 const S<T>::C;
             ^
prog.cc:8:29: note: previous declaration as 'const value_type S< <template-parameter-1-1> >::C'
     static value_type const C = 0;
                             ^
prog.cc:13:13: error: declaration of 'const value_type S< <template-parameter-1-1> >::C' outside of class is not definition [-fpermissive] const S<T>::C;

テンプレートエイリアスvalue_t<S<T>>の代わりに完全なtypename S<T>::value_typeを使用する場合、 g ++も機能します

質問:テンプレートエイリアスは、基になる式と完全に交換可能であると想定されていませんか?これはg ++のバグですか?

更新:Visual C++は、クラス外定義のエイリアステンプレートも受け入れます。

41
TemplateRex

問題はSFINAEに依存しています。外部宣言のように、メンバー関数を_value_t<S<T>>_に書き換えると、GCCはそれを喜んでコンパイルします。

_template<class T>
struct S
{
    using value_type = int;
    static const value_t<S<T>> C = 0;
};

template<class T> 
const value_t<S<T>> S<T>::C;
_

式が機能的に同等になっているためです。 substitution failureのようなものがエイリアステンプレートで機能しますが、ご覧のとおり、メンバー関数_value_type const C_には_value_t<S<T>> const S<T>::C_と同じ「プロトタイプ」がありません。最初のものはSFINAEを実行する必要はありませんが、2番目のものはそれを必要とします。したがって、明らかに両方の宣言は異なる機能を持っているため、GCCのかんしゃくです。

興味深いことに、Clangは異常の兆候なしにそれをコンパイルします。 GCCと比較して、Clangの分析の順序が逆になっているのは偶然だと思います。エイリアステンプレート式が解決されて正常になると(つまり、整形式になると)、clangは両方の宣言を比較し、それらが同等であることを確認します(この場合、両方の式が_value_type_に解決されると)。

さて、標準の目から見て正しいのはどれですか?エイリアステンプレートのSFNIAEを宣言の機能の一部として検討するかどうかは、まだ解決されていない問題です。引用 [temp.alias]/2

Template-idがエイリアステンプレートの特殊化を参照する場合、エイリアステンプレートのtype-idのtemplate-parametersをそのtemplate-argumentsに置き換えることによって取得される関連タイプと同等です。

言い換えると、これら2つは同等です。

_template<class T>
struct Alloc { /* ... */ };

template<class T>
using Vec = vector<T, Alloc<T>>;

Vec<int> v;
vector<int, Alloc<int>> u;
_

_Vec<int>_と_vector<int, Alloc<int>>_は同等のタイプです。これは、置換が実行された後、両方のタイプが_vector<int, Alloc<int>>_になるためです。 「置換後」とは、すべてのテンプレート引数がテンプレートパラメータに置き換えられた後にのみ同等性がチェックされることを意味することに注意してください。つまり、比較は、_vector<T, Alloc<T>>_のTが_Vec<int>_のintに置き換えられたときに開始されます。たぶんそれがClangが_value_t<S<T>>_で行っていることですか?しかし、 [temp.alias]/ から次の引用があります:

ただし、template-idが依存している場合、後続のテンプレート引数の置換は引き続きtemplate-idに適用されます。 【例:

_template<typename...> using void_t = void;
template<typename T> void_t<typename T::foo> f();
f<int>(); // error, int does not have a nested type foo
_

—例を終了]

問題は次のとおりです。式hasは整形式であるため、コンパイラは置換が適切かどうかを確認する必要があります。テンプレート引数の置換を実行するために依存関係がある場合(例:_typename T::foo_)、式全体の機能が変更され、「同等性」の定義が異なります。たとえば、次のコードはコンパイルされません(GCCおよびClang)。

_struct X
{
    template <typename T>
    auto foo(T) -> std::enable_if_t<sizeof(T) == 4>;
};

template <typename T>
auto X::foo(T) -> void
{}
_

外側のfooのプロトタイプは内側のプロトタイプとは機能的に異なるためです。代わりにauto X::foo(T) -> std::enable_if_t<sizeof(T) == 4>を実行すると、コードが正常にコンパイルされます。これは、fooの戻り値の型がsizeof(T) == 4の結果に依存する式であるためです。したがって、テンプレートの置換後、そのプロトタイプはそのインスタンスごとに異なる可能性があります。一方、auto X::foo(T) -> voidの戻り値の型は決して異なりません。これは、X内の宣言と競合します。これは、コードで発生しているのとまったく同じ問題です。したがって、この場合、GCCは正しいようです。

6
Mário Feroldi