yohhoyの日記

技術的メモをしていきたい日記

関数テンプレート特殊化とADLの小改善

C++2a(C++20)における特殊化された関数テンプレート呼び出しとADL(Argument Dependent Lookup)に関する小さな改善。

C++17現在の言語仕様では、タプル要素アクセス関数はstd::get<N>のように完全修飾名で呼び出す、もしくはusing std::get;により名前getを現在の名前空間へ取り込んでおく必要がある。C++2a言語仕様ではこの制限が緩和され、ADLによって期待通りstd::get関数テンプレートを見つけるようになる。

#include <tuple>

std::tuple<char, int, double> tp{'X', 42, 3.14};

// C++17: NG 名前getが見つからないためコンパイルエラー
// C++2a: OK 期待通りstd::get関数テンプレートを呼び出す
assert( get<1>(tp) == 42 );

// OK: タプルのインデクス位置1=int型要素にアクセス
assert( std::get<1>(tp) == 42 );
// または
using std::get;
assert( get<1>(tp) == 42 );

C++17仕様

tupleクラスは名前空間stdに属しており、一見するとget<1>(tp)はADLによりstd::get<1>となるように思えるが、関数テンプレートパラメータ明示されている場合はプログラマの待通りに動作しない。

ここではトーク<の存在により、“特殊化されたget<1>関数テンプレートの呼び出し” ではなく “式get < 1 > (tp)の評価*1” と解釈され、ADLは適用されずにgetは未知の名前とみなされる。C++17 17.8.1/p8より引用(下線部は強調)。

[Note: For simple function names, argument dependent lookup (6.4.2) applies even when the function name is not visible within the scope of the call. This is because the call still has the syntactic form of a function call (6.4.1). But when a function template with explicit template arguments is used, the call does not have the correct syntactic form unless there is a function template with that name visible at the point of the call. If no such name is visible, the call is not syntactically well-formed and argument-dependent lookup does not apply. If some such name is visible, argument dependent lookup applies and additional function templates may be found in other namespaces. [Example:

namespace A {
  struct B { };
  template<int X> void f(B);
}
namespace C {
  template<class T> void f(T t);
}
void g(A::B b) {
  f<3>(b);     // ill-formed: not a function call
  A::f<3>(b);  // well-formed
  C::f<3>(b);  // ill-formed; argument dependent lookup applies only to unqualified names
  using C::f;
  f<3>(b);     // well-formed because C::f is visible; then A::f is found by argument dependent
}

-- end example] -- end note]

C++2a仕様

C++2aにむけた提案文書P0846R0が採択され、本ケースのように「後続<がある場合にはテンプレート名とみなす」よう言語仕様が調整された。N4762 Working Draft 6.4.1/p3, 12.2/p2より一部引用(下線部は強調)。

The lookup for an unqualified name used as the postfix-expression of a function call is described in 6.4.2. [Note: For purposes of determining (during parsing) whether an expression is a postfix-expression for a function call, the usual name lookup rules apply. In some cases a name followed by < is treated as a template-name even though name lookup did not find a template-name (see 12.2). For example,

int h;
void g();
namespace N {
  struct A {};
  template <class T> int f(T);
  template <class T> int g(T);
  template <class T> int h(T);
}

int x = f<N::A>(N::A());  // OK: lookup of f finds nothing, f treated as template name
int y = g<N::A>(N::A());  // OK: lookup of g finds a function, g treated as template name
int z = h<N::A>(N::A());  // error: h< does not begin a template-id

(snip) -- end note]

For a template-name to be explicitly qualified by the template arguments, the name must be considered to refer to a template. [Note: Whether a name actually refers to a template cannot be known in some cases until after argument dependent lookup is done (6.4.2). --end note] A name is considered to refer to a template if name lookup finds a template-name or an overload set that contains a function template. A name is also considered to refer to a template if it is an unqualified-id followed by a < and name lookup finds either one or more functions or finds nothing.

関連URL

*1:比較演算子 < として解釈され、左項に未知の名前 get が登場することになる。