あなた自身の概念を書く方法?第1部
前の週の間に、我々はC +概念の後の動機づけと機能とクラスでそれらを使う方法について議論しました.しかし、ほとんど書かれていない.機能的に不完全な概念を定義した
この記事は、異なる種類の制約を一度に含んでいれば、あまりにも長くなります.この1つで、我々は既存のものを結合している単純な概念から始めています、そして、我々は必要な操作とクラスのAPIの一般的な要件で終わります.
来週、戻り値の型に関する要件を記述する方法、タイプ要件を表現する方法、および制約の生成方法を示します.
最終的に始めるのには時間がかかる.
単純
この例では、単に
最も簡単なコンセプトを見たので、より詳細なコンセプトを構築するために、どのビルディングブロックが我々の処分であるかを調べましょう.
定義済みの概念を使用する
おそらく、新しい概念を定義する最も簡単な方法は、既存のものを結合することです.
たとえば、次の例では、私たちは、もう一度作成するつもりです
おそらくそれは自明ですが、ユーザー定義の概念を使用することもできます.
As we saw earlier , そこにそれらを結合する無限の方法があるので、標準ライブラリの別のヘッダーで定義された概念がたくさんあります.
しかし、本当にユニークな概念を定義するには?
独自の制約を書く
次のセクションでは、我々はどのように定義済みの概念のいずれかを使用せずに独自の要件を表現する方法を掘りに行くつもりです.
オペレーションの要件
テンプレートパラメータによって、特定の操作や演算子をサポートすることを要求します.
テンプレートパラメータが追加可能であれば、そのための概念を作成することができます.
執筆
インターフェイスの単純な要件
もう少し操作を考えましょう.何がそれをサポートする必要がありますか
それは、受け入れられたタイプを機能を持っているものに制限することを意味します
それで、我々はどんな関数呼び出しの要件を定義することができなければなりませんか?
右、それを行う方法を見てみましょう.
私たちの期待は
もし渡された型が関数と呼ばれるなら
複数の関数呼び出しの妥当性についての要件があれば、セミコロンによって分離されたすべてを列挙しなければなりません.
しかし、我々は指数としてちょうど任意の整数を受け入れるようにしたい場合.意志はどこにあるか、道がある!さて、それが構文上の質問に来るとき、それは必ずしも真実でありません、しかし、我々はこのケースで幸運になりました.
まず、コンセプト
次のステップを更新することです
現在
何かミスか?はい!できません 一人で行きましょう.
明示的に指定する
そのためには定義しなければならない
ちょうど私たちが述べた通りです.
現在の完全な例を見てみましょう.
結論
今日、我々はどのように我々自身の概念を書くかについて発見し始めました.最初に、既存の概念をより複雑なものに結合した後、制約付き型の操作の妥当性についての要件を作り続けました.次に、パラメータリストの有無にかかわらず、関数呼び出しの要件を書き終えました.
次回は、戻り値の型を制約し、型を作り、入れ子になった要件を続けます.
ステイ!
あなたがC++概念についての詳細を知りたいならば、check out my book on Leanpub !
接続深い
あなたがこの記事をおもしろく見つけたならばsubscribe to my personal blog そして、接続しましょう!
Number
例のために、しかし、それはそれです.今、我々は概念においてどのような制約を表現できるかについて詳細に説明している.この記事は、異なる種類の制約を一度に含んでいれば、あまりにも長くなります.この1つで、我々は既存のものを結合している単純な概念から始めています、そして、我々は必要な操作とクラスのAPIの一般的な要件で終わります.
来週、戻り値の型に関する要件を記述する方法、タイプ要件を表現する方法、および制約の生成方法を示します.
最終的に始めるのには時間がかかる.
単純
concept
最初に想像できる簡単なコンセプトを定義しましょう.template<typename T>
concept Any = true;
まず、テンプレートパラメータを一覧表示します.T
, しかし、コンマで区切られた複数のものを持つことができました.その後、キーワードconcept,
我々は概念の名前を宣言し、その後=
概念を定義する.この例では、単に
true
, 任意の種類のT
そのコンセプトはtrue
; 任意の型を受け入れます.執筆すべきかfalse
, 何も受け入れられない.最も簡単なコンセプトを見たので、より詳細なコンセプトを構築するために、どのビルディングブロックが我々の処分であるかを調べましょう.
定義済みの概念を使用する
おそらく、新しい概念を定義する最も簡単な方法は、既存のものを結合することです.
たとえば、次の例では、私たちは、もう一度作成するつもりです
Number
整数と浮動小数点数の両方を受け入れます.#include <concepts>
template<typename T>
concept Number = std::integral<T> || std::floating_point<T>;
上記の例でわかるように、我々は容易に||
演算子2概念もちろん、任意の論理演算子を使用できます.おそらくそれは自明ですが、ユーザー定義の概念を使用することもできます.
#include <concepts>
template<typename T>
concept Integer = std::integral<T>;
template<typename T>
concept Float = std::floating_point<T>;
template<typename T>
concept Number = Integer<T> || Float<T>;
この例では、基本的にエイリアス(と間接的な層を追加しました)std::integral
and std::floating_point
ユーザー定義の概念を概念の組み合わせで使用することもできます.As we saw earlier , そこにそれらを結合する無限の方法があるので、標準ライブラリの別のヘッダーで定義された概念がたくさんあります.
しかし、本当にユニークな概念を定義するには?
独自の制約を書く
次のセクションでは、我々はどのように定義済みの概念のいずれかを使用せずに独自の要件を表現する方法を掘りに行くつもりです.
オペレーションの要件
テンプレートパラメータによって、特定の操作や演算子をサポートすることを要求します.
テンプレートパラメータが追加可能であれば、そのための概念を作成することができます.
#include <iostream>
#include <concepts>
template <typename T>
concept Addable = requires (T a, T b) {
a + b;
};
auto add(Addable auto x, Addable auto y) {
return x + y;
}
struct WrappedInt {
int m_int;
};
int main () {
std::cout << add(4, 5) << '\n';
std::cout << add(true, true) << '\n';
// std::cout << add(WrappedInt{4}, WrappedInt{5}) << '\n'; // error: use of function 'auto add(auto:11, auto:12) [with auto:11 = WrappedInt; auto:12 = WrappedInt]' with unsatisfied constraints
}
/*
9
2
*/
我々はいつそれを観察できるadd()
がパラメータWrappedInt
- 彼らが支持しないのでoperator+
- コンパイルはかなり説明的なエラーメッセージで失敗します(エラーメッセージ全体が上記の例にコピーされません).執筆
Addable
コンセプトはかなり簡単ですが、右?アフターrequires
キーワードは基本的にどんな種類の構文をコンパイルして実行するかを書き留めます.インターフェイスの単純な要件
もう少し操作を考えましょう.何がそれをサポートする必要がありますか
+
操作?それは、受け入れられたタイプを機能を持っているものに制限することを意味します
T T::operator+(const T& other) const
関数.あるいは、T T::operator+(const U& other) const
, 多分、別のタイプのインスタンスを追加したいのですが、ここではポイントではありません.私のポイントは、特定の機能を持つことについての要件を作りました.それで、我々はどんな関数呼び出しの要件を定義することができなければなりませんか?
右、それを行う方法を見てみましょう.
#include <iostream>
#include <string>
#include <concepts>
template <typename T> // 2
concept HasSquare = requires (T t) {
t.square();
};
class IntWithoutSquare {
public:
IntWithoutSquare(int num) : m_num(num) {}
private:
int m_num;
};
class IntWithSquare {
public:
IntWithSquare(int num) : m_num(num) {}
int square() {
return m_num * m_num;
}
private:
int m_num;
};
void printSquare(HasSquare auto number) { // 1
std::cout << number.square() << '\n';
}
int main() {
printSquare(IntWithoutSquare{4}); // error: use of function 'void printSquare(auto:11) [with auto:11 = IntWithoutSquare]' with unsatisfied constraints,
// the required expression 't.square()' is invalid
printSquare(IntWithSquare{5});
}
この例では、関数がありますprintSquare
(1)概念を満たすパラメータが必要である.HasSquare
(2)そのコンセプトでは、我々が期待するインターフェイスを定義するのが本当に簡単であることがわかります.アフターrequires
キーワードは、受け入れられた型のインターフェイスでどのように呼び出しをサポートするかを書き留めなければなりません.私たちの期待は
requires
キーワード.第一に、関数のための括弧の間のパラメタリストがあります--我々は制約されるすべてのテンプレートパラメタと制約に現れるかもしれない他のパラメタをリストしなければなりません.後でそれ以上.もし渡された型が関数と呼ばれるなら
square
, 我々は単に書く必要があります(T t) {t.square();}
. (T t)
のインスタンスに制約を定義したいのでT
テンプレートタイプとt.square()
なぜなら、私たちはt
タイプのインスタンスT
パブリック関数square()
.複数の関数呼び出しの妥当性についての要件があれば、セミコロンによって分離されたすべてを列挙しなければなりません.
template <typename T>
concept HasSquare = requires (T t) {
t.square();
t.sqrt();
};
パラメータは?定義しましょうpower
を取る関数int
パラメータのパラメータtemplate <typename T>
concept HasPower = requires (T t, int exponent) {
t.power(exponent);
};
// ...
void printPower(HasPower auto number) {
std::cout << number.power(3) << '\n';
}
The exponent
我々が通過する変数T::power
関数はrequires
私たちが拘束するテンプレートタイプとともに、そのタイプによるキーワード.そのように、我々はパラメータが何かであることを修正しますint
.しかし、我々は指数としてちょうど任意の整数を受け入れるようにしたい場合.意志はどこにあるか、道がある!さて、それが構文上の質問に来るとき、それは必ずしも真実でありません、しかし、我々はこのケースで幸運になりました.
まず、コンセプト
HasPower
は2つのパラメータをとるべきです.基底型と指数型の1つ.template <typename Base, typename Exponent>
concept HasPower = std::integral<Exponent> && requires (Base base, Exponent exponent) {
base.power(exponent);
};
テンプレートタイプを確認しますExponent
は積分であり、それはBase::power()
パラメータとして.次のステップを更新することです
printPower
関数.概念HasPower
変更されましたが、次の2つのタイプがあります.template<typename Exponent>
void printPower(HasPower<Exponent> auto number, Exponent exponent) {
std::cout << number.power(exponent) << '\n';
}
ASExponent
テンプレート型パラメーターとして明示的に表示されますauto
後にキーワード.一方、auto
が必要ですHasPower
, さもなければ、我々はそれが概念であるということを知っています、そして、特定のタイプでない?ASExponent
テンプレート型パラメータとして渡されますHasPower
制約もそれにも適用されます.現在
printPower
を呼び出すことができますIntWithSquare
to IntWithPower
APIの変更に続いてprintPower(IntWithPower{5}, 3);
printPower(IntWithPower{5}, 4L);
同時に、呼び出しprintPower(IntWithPower{5}, 3.0);
は失敗するfloat
積分性の制約は満たさない.何かミスか?はい!できません
IntWithPower
指数として.我々は、コールすることができますBase::power(Exponent exp)
カスタムタイプでIntWithPower
そのためには二つのことが必要です.IntWithPower
はintegral
種類IntWithPower
受け入れられるものに転換するべきであるpow
からcmath
ヘッダ.明示的に指定する
type_trait
std::is_integral
for IntWithPower
, 私たちはIntWithPower
積分型.もちろん、私たちが実際の生活の中でそうするつもりならば、私たちのタイプが積分型のすべての特性を持っていることを確認するのがよりよいです、しかし、それはここで我々の範囲を越えています.template<>
struct std::is_integral<IntWithPower> : public std::integral_constant<bool, true> {};
今、我々はそれを確認する必要がありますIntWithPower
に変換されます pow
. 浮動小数点型を受け入れますが、IntWithPower
, 私の意見では、それをAに変えることはより意味がありますint
そして、コンパイラに暗黙の変換を実行させますfloat
- 一般的に暗黙の変換を避ける方が良いですが.でも結局はIntWithPower
他の文脈でも使われるかもしれません--整数として.そのためには定義しなければならない
operator int
:class IntWithPower {
public:
IntWithPower(int num) : m_num(num) {}
int power(IntWithPower exp) {
return pow(m_num, exp);
}
operator int() const {return m_num;}
private:
int m_num;
}
今の例をチェックすると、両方ともprintPower(IntWithPower{5}, IntWithPower{4});
and printPower(IntWithPower{5}, 4L);
がコンパイルされるが、printPower(IntWithPower{5}, 3.0);
失敗する3.0
は積分されません.ちょうど私たちが述べた通りです.
pow
浮動小数点数で動作しますが、我々だけで積分を受け入れる.それに従って我々の概念を更新しましょう!template <typename Base, typename Exponent>
concept HasPower = (std::integral<Exponent> || std::floating_point<Exponent>) && requires (Base base, Exponent exponent) {
base.power(exponent);
};
今すぐコールできますprintPower
任意の種類のbase
それはHasPower
指数としての積分と浮動小数点数の両方の概念.現在の完全な例を見てみましょう.
#include <cmath>
#include <iostream>
#include <string>
#include <concepts>
#include <type_traits>
template <typename Base, typename Exponent>
concept HasPower = (std::integral<Exponent> || std::floating_point<Exponent>) && requires (Base base, Exponent exponent) {
base.power(exponent);
};
class IntWithPower {
public:
IntWithPower(int num) : m_num(num) {}
int power(IntWithPower exp) {
return pow(m_num, exp);
}
operator int() const {return m_num;}
private:
int m_num;
};
template<>
struct std::is_integral<IntWithPower> : public std::integral_constant<bool, true> {};
template<typename Exponent>
void printPower(HasPower<Exponent> auto number, Exponent exponent) {
std::cout << number.power(exponent) << '\n';
}
int main() {
printPower(IntWithPower{5}, IntWithPower{4});
printPower(IntWithPower{5}, 4L);
printPower(IntWithPower{5}, 3.0);
}
この例では、異なる制約付き型のパラメーターを受け入れることができる特定の関数の存在を期待する概念を記述する方法を観察できます.また、組み込み型の型を満足させるような型を作る方法を見ることができますstd::is_integral
.結論
今日、我々はどのように我々自身の概念を書くかについて発見し始めました.最初に、既存の概念をより複雑なものに結合した後、制約付き型の操作の妥当性についての要件を作り続けました.次に、パラメータリストの有無にかかわらず、関数呼び出しの要件を書き終えました.
次回は、戻り値の型を制約し、型を作り、入れ子になった要件を続けます.
ステイ!
あなたがC++概念についての詳細を知りたいならば、check out my book on Leanpub !
接続深い
あなたがこの記事をおもしろく見つけたならばsubscribe to my personal blog そして、接続しましょう!
Reference
この問題について(あなた自身の概念を書く方法?第1部), 我々は、より多くの情報をここで見つけました https://dev.to/sandordargo/how-to-write-your-own-concepts-part-i-33a9テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol