C++2a(C++20)コンセプト requires式(requires-expression) に記述する 複合要件(compound-requirement) では「ある式の評価結果が特定コンセプトを満たすこと」を制約するが、このとき標準コンセプトstd::same_as
/std::convertible_to
を適切に使い分ける必要がある。特に “データメンバ型の制約” には注意すること。
下記コードのコンセプトC0
では式t.data
がint
型へと変換可能(convertible_to<int>
)と制約しているが、コンセプトC1
のようにint
型と等しい(same_as<int>
)と制約するとプログラマの期待通りに動作しない。
// C++2a #include <concepts> struct S { int data; // int型のデータメンバ int& mf(); // int&型を返すメンバ関数 }; template <typename T> concept C0 = requires (T t) { // T型の値tはデータメンバdataをもち、int型へと変換可能 { t.data } -> std::convertible_to<int>; // T型の値tはメンバ関数mf()をもち、呼び出し結果はint&型に等しい { t.mf() } -> std::same_as<int&>; }; static_assert( C0<S> ); // OK template <typename T> concept C1 = requires (T t) { // T型の値tはデータメンバdataをもち、"式t.data"はint型に等しい { t.data } -> std::same_as<int>; // ... }; static_assert( C1<S> ); // NG: 式t.dataの型はint&
戻り値型を制約する複合要件{ E } -> C<U>;
は、2つの制約E; requires C<decltype((E)), U>;
*1と等価である。ここでdecltype指定子オペランドが括弧付きの式(E)
となることに留意。(→id:yohhoy:20200817)
template <typename T> concept C1 = requires (T t) { // { t.data } -> std::same_as<int>; と等価な制約 t.data; requires std::same_as<decltype((t.data)), int>; // ... };
クラスのデータメンバ型をsame_as
コンセプトで制約する場合は、下記コンセプトC2
のように入れ子要件(nested-requirement)を利用する。*2
template <typename T> concept C2 = requires { // メンバT::dataはint型に等しい requires std::same_as<decltype(T::data), int>; // ... } static_assert( C2<S> ); // OK
下記コンセプトC3
のように複合要件とsame_as
コンセプトを組み合わせた場合、データメンバ型はint
またはint&
いずれも制約を満たす。これはsame_as
コンセプト利用意図からすると、不適切な制約表現といえる。*3
struct S1 { int data; // int型のデータメンバ }; struct S2 { int& data; // int&型のデータメンバ }; template <typename T> concept C3 = requires (T t) { { t.data } -> std::same_as<int&>; }; // OK: データメンバが int型 または int&型 であれば制約を満たす static_assert( C3<S1> && C3<S2> );
N4861 7.5.7.3/p1, 18.4.2, 18.4.4/p1より一部引用。
compound-requirement:
{
expression}
noexcept
opt return-type-requirementopt;
return-type-requirement:
->
type-constraint
A compound-requirement asserts properties of the expression
E
. Substitution of template arguments (if any) and verification of semantic properties proceed in the following order:
- Substitution of template arguments (if any) into the expression is performed.
- If the
noexcept
specifier is present,E
shall not be a potentially-throwing expression (14.5).- If the return-type-requirement is present, then:
- Substitution of template arguments (if any) into the return-type-requirement is performed.
- The immediately-declared constraint (13.2) of the type-constraint for
decltype((E))
shall be satisfied. [Example: Given conceptsC
andD
,requires { { E1 } -> C; { E2 } -> D<A1, ..., An>; };is equivalent to
requires { E1; requires C<decltype((E1))>; E2; requires D<decltype((E2)), A1, ..., An>; };(including in the case where n is zero). -- end example]
template<class T, class U> concept same-as-impl = is_same_v<T, U>; // exposition only template<class T, class U> concept same_as = same-as-impl <T, U> && same-as-impl <U, T>;[Note:
same_as<T, U>
subsumessame_as<U, T>
and vice versa. -- end note]
Given types
From
andTo
and an expressionE
such thatdecltype((E))
isadd_rvalue_reference_t<From>
,convertible_to<From, To>
requiresE
to be both implicitly and explicitly convertible to typeTo
. The implicit and explicit conversions are required to produce equal results.template<class From, class To> concept convertible_to = is_convertible_v<From, To> && requires(add_rvalue_reference_t<From> (&f)()) { static_cast<To>(f()); };
関連URL
- P1452R2 On the non-uniform semantics of return-type-requirements
- c++20 - C++ concepts compound requirements and return-type-requirements - Stack Overflow
- c++17 - How to use a C++ requires clause in a concept to require a member variable to satisfy a concept constraint? - Stack Overflow
- c++ - What's the best way to express concept requirements for data members in a concept? - Stack Overflow
- cpprefernce: Constraints and concepts, std::same_as, std::convertible_to
- cpprefjp: コンセプト, std::same_as, std::convertible_to
- https://twitter.com/yohhoy/status/1187167956419039232
*1:単純要件(simple-requirement)と入れ子要件(nested-requirement)の2つで表現される。
*2:制約が std::same_as<decltype(T::data), int> のみで構成される場合は requires 式の入れ子要件とする必要はなく、直接 template <typename T> concept C2 = std::same_as<decltype(T::data), int>; と記述すればよい。
*3:ソースコード上は “参照型と等しい” と読み取れる記述がなされ、実際には参照型/値型の両方を許容するコンセプトはトラブルの元になる可能性が高い。