yohhoyの日記

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

可変長コンセプト×畳み込み式: The glass is half full or half empty?

C++20コンセプトと論理演算子(&&, ||)による畳み込み式(fold expression)の関係について。本記事の内容はStackOverflowで見つけた質問と回答に基づく。

まとめ:&&||による畳み込み式を用いた制約式(constraint-expression)は機能するものの、コンセプト間の包摂関係(subsumption relation)は期待通りに成り立たない。

#include <concepts>

// コンセプト any_of<T, Us...>
// 型Tが型リストUs..のいずれかと一致する?
template <typename T, typename... Us>
concept any_of = (std::same_as<T, Us> || ...);

template <typename T>
constexpr int f(T) { return 1; }  // #1

template <typename T>
  requires any_of<T, int, double>
constexpr int f(T) { return 2; }  // #2
// または
template <any_of<int, double> T> 
constexpr int f(T) { return 2; }  // #2

static_assert(f(42) == 2);    // OK: #2 int型
static_assert(f(3.14) == 2);  // OK: #2 double型
static_assert(f(0.f) == 1);   // OK: #1 float型
static_assert(f('X') == 1);   // OK: #1 char型

C++20コンセプト仕様では、畳み込み式(std::same_as<T, Us> || ...)それ自体で一つの原始制約(atomic constraint)を構成する。オーバーロード解決のために制約式の包摂関係を求める(≒強弱の判定)正規化(normalization)過程で、std::same_as<T, Us[0]>std::same_as<T, Us[1]>...のようには展開解釈されない。*1

// 畳み込み式による(可変長)コンセプト定義
template <typename T, typename... Us>
concept any_of = (std::same_as<T, Us> || ...);

template <any_of<int, double> T> 
constexpr int f(T) { return 2; }  // #2

template <std::same_as<double> T>
constexpr int f(T) { return 3; }  // #3

static_assert(f(42) == 2);    // OK: #2の制約のみ満たす
static_assert(f(3.14) == 3);  // NG: #2,#3間でオーバロード解決が曖昧

可変長コンセプトをあきらめて制約式(std::same_as<T, U1> || std::same_as<T, U2>)とすれば包摂関係が成立し、自然なオーバーロード選択が行われる。つまり制約any_of2<T, int, double>よりも制約std::same_as<T, double>の方がより強い制約(more constrained)と解釈される。

// 畳み込み式を利用しない(2型版)コンセプト定義
template <typename T, typename U1, typename U2>
concept any_of2 = (std::same_as<T, U1> || std::same_as<T, U2>);

template <any_of2<int, double> T> 
constexpr int f(T) { return 2; }  // #2

template <std::same_as<double> T>
constexpr int f(T) { return 3; }  // #3

static_assert(f(42) == 2);    // OK: #2
static_assert(f(3.14) == 3);  // OK: #3は#2より強く制約される

関連URL

*1:C++20 §13.5.1.1/p1 Note: "(snip) For the purpose of exposition, conjunction is spelled using the symbol ⋀ and disjunction is spelled using the symbol ⋁. (snip)"