以下の記事において、traits/policy とそれを取り巻く概念についてよく分からなくなったので、改めて調べてみたのですが、たぶん一つの結論を得ました。
Re2: C++のscoped enumで関数のフラグ指定をしたい & 君の名は・・・enum class - Qiita
1 関係
敢えて関係を描くとすれば以下の様になるでしょうか
+--------+ +--------------+
| Policy | ⊂ | 広義Strategy |
+--------+ +--------------+
|
利用
↓
+--------+ +------------+
| Traits | ⊂ | 広義Traits |
+--------+ +------------+
+-----------------------+
| 関数・クラスのContract |
+-----------------------+
↑
記述
|
+-----------------------------------+
| Named Requirements ~ 型のContract |
+-----------------------------------+
↑
記述
|
+-----------+ +------------+
| `concept` | ⊂ | 広義Traits |
+-----------+ +------------+
でもこれだけ見てもよく分からないですね…。それぞれの詳細については以降を御覧ください。
2 各概念・手法について
結局、やはり traits/policy は人によって違う説を唱えていて、正しい答えがあるというものではないように思われます。しかし、多くの説が判然としない精神論(?)で煙に巻こうとしている(?)中で、一つ的確ですっと腑に落ちる説明があったのでそれを採用します。(他の幾つかの説については、このページの下部の附録でまとめました。)
##2.1 Strategy
struct MyClass {
IWanWanStrategy* strategy;
void wanwan() const {
strategy->do_wanwan();
}
};
こういうのです。精神論は語りません。上記のコード以上でも以下でもありません。
##2.2 Policy
a compile-time variant of the strategy pattern (引用: Wikipedia) と思うのが良さそうです。
template<typename WanWanPolicy>
struct MyClass {
void wanwan() const {
WanWanPolicy::do_wanwan();
}
};
こういうのの事です。Policy は実は、テンプレートかどうかだとか、特定の型と結びついているかどうかだとかと全然関係ありません。Modern C++ Design や Wikipedia にある様にコンパイル時 (広義)Strategy と見ることができます。
- Policy-based design - Wikipedia
- Sec. 1.5, "Modern C++ Design: Generic Programming and Design Patterns Applied (C++ in Depth Series)", Andrei Alexandrescu, Addison-Wesley Professional (2001)
##2.3 Traits
template<typename T> struct MyTraits;
template<> struct MyTraits<int> {
typedef int type;
static constexpr int value = 11;
static void do_wanwan() const {std::cout << "wanwan" << std::endl;}
};
template<> struct MyTraits<double> { /* 略 */ };
template<typename T> struct MyTraits<T*> { /* 略 */ };
// ...
型ごとに性質の記述・設定を行うための仕組み (広義Traits) です。特に現行 C++ ではtemplate specialization (Traits) を用います。傾向として nested types や static data members を持ちますが、static member functions を持ってはいけないという事はないように思います。
- Traits, Nathan C. Myers, 1995 (※標準化前の文書なので特殊化方法が今と異なります)
- Trait (computer programming) - Wikipedia
##2.4 混乱の原因
template<typename T, typename Policy = MyTraits<T>>
struct MyClass {
void wanwan() const {
Policy::do_wanwan();
}
};
標準ライブラリでも広範に見られるこの組み合わせパターンです。"デフォルトの Policy を Traits で与えておくと便利だよ" パターンです。
混乱を招いているのは、Policy を説明する時に、どの説明もいきなりこの Policy と Traits を組み合わせた物を提示することです。そうすると読者は Traits まで含めて Policy なのだと勘違いしてしまいます。そして、個々のクラスについて Traits なのか Policy なのかという誤った問いが発生するのです。
しかし、実際は Traits はクラスの定義の形態を表すもので、Policy はクラスの使われ方を表すもので、両者は異なる観点に従った分類です。そして一つのクラスが Traits として定義され Policy として使われるということも可能です。「Traits なのか Policy なのか」という問いは「30歳なのか警察官なのか」という問いの如く、意味のないものです。
##2.5 Concepts?
Concepts も Traits などと関連付けられそうでちょっと違うようなよく分からないものです。考えてみたのですけれど、よく分からない原因の一つは STL の Concepts と、それを文法レベルで表現しようとする concept
を混ぜて考えるからではないかという気がしてきました。
そういう訳で、ここでは前者を Named Requirements と呼び、後者を concept
と呼ぶことにします。
##2.6 Named Requirements
Edit (2020-03-04): この概念は以前は巷では Library Concepts と呼ばれていましたが、C++17 ([utility.arg.requirements]/1) で登場した Named Requirements という語が現在では広く使われているようです。
Named Requirements はライブラリで型に要求する約束事に単に名前をつけただけのもので、C++ の文法レベルで提供されるものではありません。例えば、CopyConstructible requirements は規格に以下の様に記述されています。
Table 21 — CopyConstructible requirements (in addition to MoveConstructible) [copyconstructible]
Expression | Post-condition |
---|---|
T u = v; |
the value of v is unchanged and is equivalent to u
|
T(v) |
the value of v is unchanged and is equivalent to T(v)
|
以下に Named Requirements の一覧があります。
##2.7 concept
Named Requirements は或る物は <type_traits>
によって狭義Traitsとして提供され、また或るものは将来の concept
によって文法レベルで記述されます。concept
は(広義の)Traits と言える気がします。
##2.8 Contract
関数(など)とその利用者の間で (暗黙的もしくは明示的に) 仮定される約束事です。例えば、25.2.4/1
25.2.4 For each [alg.foreach]
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f);
1 Requires: Function shall meet the requirements of MoveConstructible (Table 20). [Note: Function need not meet the requirements of CopyConstructible (Table 21). —end note ]
の Requires の部分がそれです。MoveConstructible/CopyConstructible などの Named Requirements を使って記述されています。規格を見ると他にも Post-condition や Precondition などといったものも見えます。因みに、concept
は明示的な Contract の記述 (テンプレート引数の制限) をサポートする仕組みも持ちます。
また、Named Requirements 自体も型に対する Contract と見れそうです。
##2.9 Traits vs Trait (おまけ)
これも人によってぶれがあるようですが、大体において、クラステンプレート (実体化前) は traits で、テンプレートクラス (実体化後) は a trait と使い分けているようです。明示的特殊化は a trait な気がします。
##2.10 まとめ
- Strategy は動作の設定の一部を外部に委譲する仕組みである
- Policy はテンプレート引数を使った (広義) Strategy の一種である
- (広義)Traits は型ごとに記述・設定を行う方法論である
- (狭義)Traits はテンプレート特殊化を使った広義 Traits の実現手法である
- Contract は機能を利用するときの約束事(仕様)である
- Named Requirements は関数の Contract の記述に使われる
-
concept
は広義 Traits の一種であり、Named Requirements を C++ で記述し、更に Contract として利用することの支援を目的とする
#A 附録
A.1 百家争鳴の traits vs policies^
調べてみるとやはり人によって言っていることが違うようです。何れにしても、多くの説明が Traits/Policy の定義を与えずに、その性質 (目的、傾向、云々) をだらだらと説明するもので、読んでも結局分かったような分からない様な後味です。
###A.1.1 Sec. 8.4 Traits and Policies ("C++ In a Nutshell", Ray Lischner, O'Reilly Media (2003) の転記と思われる)
Traits/Policy の目的などをひとしきり述べた後で以下のように言っています。
Traits define type interfaces, and policies define function interfaces, so they are closely related. Sometimes, a single class template implements traits and policies.
要するに nested type を公開するのが traits で member functions を公開するのが policy で、一つのクラステンプレートが traits 兼 policies であることもあるという主張のようです。
###A.1.2 回答者 TemplateRex @ c++ - What is the difference between a trait and a policy? - Stack Overflow
長々と説明していますが結局 traits, policies をそれぞれ説明するだけで関係性については述べていないように見えます。
更に他の回答なども見渡してみると、回答やコメントによって異なる観点で主張をしていたりして、皆微妙に噛み合っていない様な雰囲気です。
###A.1.3 回答者 Andy Prowl @ c++ - What is the difference between a trait and a policy? - Stack Overflow
"Modern C++ Design", Andrei Alexandrescu を見よと言い、説明を始めています。
読んでみると…確かに言われてみればそうです…何で今までこの見方に気づかなかったのでしょう。目から鱗が落ちました。この記事の本文でまとめたのはこれです。
###A.1.4 Policy-based design - Wikipedia
It has been described as a compile-time variant of the strategy pattern, and has connections with C++ template metaprogramming. It was first popularized by Andrei Alexandrescu with his 2001 book Modern C++ Design and his column Generic<Programming> in the C/C++ Users Journal.
要約: Policy-based design は "Modern C++ Design/Generic<Programming>" によって世に知らしめられ、コンパイル時 Strategy パターンである
と簡潔に述べられています。これは上の SO の回答の内容と同じですが、Policy についての的確で必要十分な説明です。
###A.1.5 Sec. 1.5, "Modern C++ Design", Andrei Alexandrescu
上の2項目で Modern C++ Design をポイントしているので、さぞかし簡潔に説明しているのだろうと思って探すと…
Policies and policy classes help in implementing safe, efficient, and highly customizable design elements. A policy defines a class interface or a class template interface. The interface consists of one or all of the following: inner type definitions, member functions, and member variables.
Policies have much in common with traits but differ in that they put less emphasis on type and more emphasis on behavior. Also, policies are reminiscent of the Strategy design pattern, with the twist that policies are compile-time bound.
重要なことも言っていますが無駄なことも言っている気がします。多分、いちばん重要なのは一番最後の文であるにも拘らず、前半部分に目を奪われてしまってそちらが本質であるように見えてしまうのがいけないのでしょう。
###A.1.6 C++におけるTraits手法の起源 - Faith and Brave - C++で遊ぼう
ここでは char_traits
は Traits ではなく Policy で、iterator_traits
は Traits と述べています。恐らく C++ In a Nutshell と同様の解釈に依拠するものでしょうか。(→ 同じ著者による次の項目 A.1.7 も参照)
興味深い文書 Traits (Policy については述べていませんが) も紹介されています。
###A.1.7 Chapter 6 ポリシー, "C++テンプレートテクニック" (第1版), επιστημη+高橋晶 (2016/10/27 18:50 追記)
Strategy PatternでC++の大づかみ - Qiita の記事を見て、C++テンプレートテクニックのことを思い出したので掘り出して見てみた所、6章の初めにちゃんと以下のように書かれていました。
その手法からポリシーは、しばしばクラス内での処理を実行時に切り替える「Strategy デザインパターン (以下、Strategy パターン)」と比較されます。
A.1.6 と同じ著者なのでこの2つの記述を総合すると、テンプレート引数に指定する Strategy が Policy で、そうでないものが Traits という区別の仕方をしているのではないかと思われます。
###A.1.8 総評
結局、色々余計なことを言いたくなってしまう人の性によってよく分からない説明ができあがり、更にそれを読んで解釈した人が新定義による二次説明を書いて余計に分からなくなっているという印象です。