C++言語における定数値を返すメタ関数X<T>::valueとその変数テンプレート版X_v<T>の微妙な非互換性について。
下記コードにおいて、[B]からインスタンス化される変数テンプレート定義の初期化子(initializer)は “immediate context” ではないため、パラメータ置換後の不完全型tuple_size<int>におけるvalueメンバ不在がコンパイルエラー(ill-formed)を引き起こす。[A] requires節の原子制約(atomic constraint)に置換した結果、その “immediate context” で不正な型や式が出現した場合、エラーとならずに制約が満たされない(not satisfied)と判断される。
#include <utility> // tuple_size<T>メタ関数 template<class T> constexpr int f() { return 1; } template<class T> requires (std::tuple_size<T>::value == 2) // [A]★ constexpr int f() { return 2; } static_assert( f<int>() == 1 ); // OK static_assert( f<std::pair<int,int>>() == 2 ); // OK // tuple_size_v<T>変数テンプレート template<class T> constexpr int g() { return 1; } template<class T> requires (std::tuple_size_v<T> == 2) // [B]★ constexpr int g() { return 2; } static_assert( g<int>() == 1 ); // NG: ill-formed static_assert( g<std::pair<int,int>>() == 2 ); // OK
C++20/23
C++23現在は用語 “immediate context” が定義されないまま利用されており、長らく(2014年から!) CWG 1844 で問題提起されていた。C++20 13.5.1.2/p3, 13.10.2/p8, 20.5.2, 20.5.6/p4-5*1より一部引用(下線部は強調)。
3 To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion (7.3.1) is performed if necessary, and
Eshall be a constant expression of typebool. The constraint is satisfied if and only if evaluation ofEresults intrue. (snip)
8 If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. -- end note] Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure. [Note: The substitution into types and expressions can result in effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the "immediate context" and can result in the program being ill-formed. -- end note]
namespace std { // ... template<class T> struct tuple_size; // not defined template<class T> struct tuple_size<const T>; // ... template<class T> inline constexpr size_t tuple_size_v = tuple_size<T>::value; }
template<class T> struct tuple_size<const T>;4 Let
TSdenotetuple_size<T>of the cv-unqualified typeT. If the expressionTS::valueis well-formed when treated as an unevaluated operand, then each specialization of the template meets the Cpp17UnaryTypeTrait requirements (20.15.1) with a base characteristic of
integral_constant<size_t, TS::value>
Otherwise, it has no membervalue.
5 Access checking is performed as if in a context unrelated toTSandT. Only the validity of the immediate context of the expression is considered. [Note: The compilation of the expression can result in side effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such side effects are not in the "immediate context" and can result in the program being ill-formed. -- end note]
C++26
C++2c(C++26)では提案文書P4149R1が採択され、用語 “immediate context” が明確に定義される。N5050 13.5.2.3/p3, 13.10.3.1/p7-8より一部引用(下線部は強調)。*2
3 To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression in the immediate context of the atomic constraint (13.10.3.1), the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion (7.3.2) is performed if necessary, and
Eshall be a constant expression of typebool. The constraint is satisfied if and only if evaluation ofEresults intrue. (snip)
7 The deduction substitution loci are
- the function type outside of the noexcept-specifier,
- the explicit-specifier,
- the template parameter declarations, and
- the template argument list of a partial specialization (13.7.6.1).
The substitution occurs in all types and expressions that are used in the deduction substitution loci. The expressions include not only constant expressions such as those that appear in array bounds or as constant template arguments but also general expressions (i.e., non-constant expressions) inside
sizeof,decltype, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. If substitution into different declarations of the same function template would cause template instantiations to occur in a different order or not at all, the program is ill-formed; no diagnostic required.
(snip)
8 If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written in the same context using the substituted arguments.
[Note 5: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. -- end note]
Invalid types and expressions can result in a deduction failure only in the immediate context of the deduction substitution loci. The immediate context of a deduction substitution locus is that deduction substitution locus, excluding bodies of lambda-expressions and separately instantiated constructs.
(snip)
[Note 6: Separately instantiated constructs are excluded from the immediate contexts even if they are instantiated at the same time. -- end note]
[Note 7: The substitution into types and expressions can result in effects such as the instantiation of template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the immediate context and can result in the program being ill-formed. -- end note]
関連URL
- P4149R1 Define "immediate context"
- c++ - What exactly is the "immediate context" mentioned in the C++11 Standard for which SFINAE applies? - Stack Overflow
- c++ - Why does GCC succeeds on a constraint with std::tuple_size::value, but fails on one with std::tuple_size_v? - Stack Overflow
- https://x.com/yohhoy/status/2096027254098935944
- コンセプトと短絡評価 - yohhoyの日記