gccで問題を特定するために数え切れないほどの時間を無駄にしました。他のコンパイラでコードベースをテストして、 Clang が見落とした可能性のある警告をさらに探したかったのです。テンプレートの引数の推定に失敗したため、プロジェクトのほぼ半分がコンパイルを停止したことにショックを受けました。ここでは、最も単純なコードの部分にまで私のケースをばらばらにしようとしました。
#include <type_traits>
struct Foo
{ };
// This is a template function declaration, where second template argument declared without a default
template <typename T, typename>
void foo(const Foo & foo, T t);
// This is a template function definition; second template argument now has a default declared
template <typename T, typename = typename std::enable_if<1>::type>
void foo(const Foo & foo, T t)
{
}
int main(int argc, char ** argv)
{
foo(Foo{}, 1);
return 0;
}
1
のstd::enable_if<1>
は無視してください。明らかに、それが問題ではない場合に物事を複雑にしないために、それは一定の値です。
このコードの一部はコンパイルされます [1] withclang(3.4から4.0)、 icc(16、17)、Visual C++(19.00.23506)。基本的に、gcc(4.8から7.1)を除いて、このコードをコンパイルしない他のc ++ 11コンパイラは見つかりませんでした。
問題は、ここで誰が正しく、誰が間違っているかです。 gccは標準に従って動作しますか?
明らかにこれは重要な問題ではありません。 std::enable_if
を宣言に簡単に移動できます。唯一の犠牲者は美学です。しかし、実装で、ライブラリ関数のユーザーに直接関係のないコードのstd::enable_if
の醜い100文字を隠すことができるのは素晴らしいことです。
godbolt.org のライブ例。
規格の内容( [1] 350ページ):
テンプレートの宣言または定義で使用できるデフォルトのテンプレート引数のセットは、定義からのデフォルト引数(スコープ内にある場合)とスコープ内のすべての宣言を、デフォルトの関数引数と同じ方法でマージすることによって取得されます(8.3.6)。 [例:
template<class T1, class T2 = int> class A; template<class T1 = int, class T2> class A; is equivalent to template<class T1 = int, class T2 = int> class A;
—最後の例]
したがって、GCCはここでは間違っています。宣言内のデフォルトのテンプレート引数は無視されます。
すべての宣言ではなく、関数テンプレート宣言のみ。クラステンプレートの宣言は問題ありません。
#include <type_traits>
template <typename T, typename>
struct Foo;
template <typename T, typename = typename std::enable_if<1>::type>
struct Foo
{
T t;
};
int main()
{
Foo<int> foo;
return 0;
}
godbolt.org の実例
おそらくそれは、デフォルト以外の引数がどのように推定されるかという性質によるものです。関数テンプレートでは、関数の引数から差し引かれます。クラステンプレートでは、明示的に指定する必要があります。
とにかく バグレポート を作成しました。