C++

テンプレートテンプレートパラメータでデフォルト引数を使うとコンパイルエラーになるのは準拠する規格が古いため

以下のコードについて考える.

#include <vector>

template< typename ValueType, template< class, class > class Container >
class c
{
    private:
    Container< ValueType > m_container;
};

int main() { c< int, std::vector >(); }

上記のコードは, std::vector の第 2 テンプレート型引数に対してデフォルトテンプレート引数 std::allocator< ValueType > が渡される事を期待したコードであるが, 引数を 2 つ取るクラステンプレート Container に対して 1 つしかテンプレート引数を渡していない, というビルドエラーとなる.

ここで, 関数 main の定義を以下のように書き換える.

int main() {}

これでもエラーは解消しない. これはクラステンプレート c に対して実テンプレート引数を与えなく4てもエラーになる, 即ちクラス c のデータメンバ m_container の名前探索がクラステンプレートの定義時に行われている事を示している.

N3797 14.6 Name resolution 項 9 に拠れば

When looking for the declaration of a name used in a template definition, the usual lookup rules (3.4.1, 3.4.2) are used for non-dependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known (14.6.2).

とあるので, データメンバ c::m_container の名前探索は, 実際にクラステンプレート c に対してテンプレート実引数を与えた時まで遅延されなければならないと思うのだが, 構文解析の都合でエラーとしている物と思われる. 規格上, 上記のコードをエラーとする根拠は分からなかった.

ただし, 規格上同等のコードは以下のように書く事が想定されている.

#include <vector>

template< typename ValueType, template< class > class Container >
class c
{
    private:
    Container< ValueType > m_container;
};

int main() { c< int, std::vector >(); }

上記のコードは gcc 8.0.0, gcc 7.1.0 ではビルドが通る. clang では通らない. 上記の書き方については, 古い規格と新しい規格とで異なっている.

以下は N3797 14.3.3 Template template arguments 項2 より引用.

template<class T> class A { / ... / };
template<class T, class U = T> class B { / ... / };
template<class ... Types> class C { / ... / };
template<template<class> class P> class X { / ... / };
template<template<class ...> class Q> class Y { / ... / };
X<A> xa; // OK
X<B> xb; // ill-formed: default arguments for the parameters of a template argument are ignored
X<C> xc; // ill-formed: a template parameter pack does not match a template parameter
Y<A> ya; // OK
Y<B> yb; // OK
Y<C> yc; // OK

以下は N4659 17.3.3 Template template arguments 項 3 より引用.

template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template<class ... Types> class C { /* ... */ };
template<auto n> class D { /* ... */ };
template<template<class> class P> class X { /* ... */ };
template<template<class ...> class Q> class Y { /* ... */ };
template<template<int> class R> class Z { /* ... */ };
X<A> xa; // OK
X<B> xb; // OK
X<C> xc; // OK
Y<A> ya; // OK
Y<B> yb; // OK
Y<C> yc; // OK
Z<D> zd; // OK

N3797 の 14.3.3 と N4659 の 17.3.3 を比較すると, N4659 では項 4 が追加されている他, 項 3 の前半が異なる.

N3797 では

A template-argument matches a template template-parameter (call it P) when each of the template parameters in the template-parameter-list of the template-argument’s corresponding class template or alias template (call it A) matches the corresponding template parameter in the template-parameter-list of P.

となっている部分が N4659 では

A template-argument matches a template template-parameter P when P is at least as specialized as the template-argument A. If P contains a parameter pack, then A also matches P if each of A’s template parameters matches the corresponding template parameter in the template-parameter-list of P.

となっている. この "at least as specialized as the template-argument A" と書かれている部分をどう解釈すべきかが曲者で, C++ 的に「特殊化」と考えたくなるがここは「適合させる」と考えるべきであろう.