あなた自身の概念を書く方法?第1部


前の週の間に、我々はC +概念の後の動機づけと機能とクラスでそれらを使う方法について議論しました.しかし、ほとんど書かれていない.機能的に不完全な概念を定義した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 そのためには二つのことが必要です.
  • IntWithPowerintegral 種類
  • 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 そして、接続しましょう!