yohhoyの日記

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

requires式中でのコンセプト制約表現には要注意

C++20 requires式(requires-expression) において、コンセプトや条件式を用いた制約(constraints)表現には注意が必要。
2022-05-08追記:gcc(g++) 12.1から警告 -Wmissing-requires が追加され、本記事で言及しているrequiresキーワード指定忘れの可能性を検知できる。本警告は既定で有効化される。*1

下記コードのように式std::signed_integral<decltype(N)>N == 42をrequires式中に単に記載すると単純要件(simple-requirement)となり、式の妥当性のみがチェックされプログラマが意図する制約は行われない。std::signed_integral<decltype(N)>N == 42の評価結果は利用されず、それ自身は常に有効な式となるため。*2

#include <concepts>

// 符号付き整数型の定数42を表すコンセプト(?)
template <auto N>
concept magic_number = requires {
  std::signed_integral<decltype(N)>;  // ★
  N == 42;  // ★
};

static_assert(  magic_number<42> );   // OK
static_assert( !magic_number<42u> );  // NG !?
static_assert( !magic_number<0> );    // NG !?

requiresキーワードを用いて入れ子要件(nested-requirement)として記述とするか、単に制約式の&&(conjunction; 連言/合接)として記述する。両記述はセマンティクス上ほぼ等価だが、後者の方がコンセプトベースのオーバーロード解決における包摂関係(→id:yohhoy:20190903)を自然に表現できる*3。Simpe is the best.

// 符号付き整数型の定数42を表すコンセプト
template <auto N>
concept magic_number = requires {
  requires std::signed_integral<decltype(N)>;
  requires (N == 42);
};
// または
template <auto N>
concept magic_number = std::signed_integral<decltype(N)> && (N == 42);

static_assert(  magic_number<42> );   // OK
static_assert( !magic_number<42u> );  // OK
static_assert( !magic_number<0> );    // OK

関連URL

*1:https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=e18e56c7

*2:C++20 13.3/p8: "A concept-id is a simple-template-id where the template-name is a concept-name. A concept-id is a prvalue of type bool, and does not name a template specialization. A concept-id evaluates to true if the concept's normalized constraint-expression is satisfied by the specified template arguments and false otherwise."

*3:https://wandbox.org/permlink/xhGW29ycmxLWJIR7