コンセプト - プレースホルダー

C++11 には、制約のない自動プレースホルダーがあります。 C++20 の概念を制約付きプレースホルダーとして使用できます。一見それほどスリリングではないように見えることは、私にとって決定的なクォンタム・リープです。 C++ テンプレートは使いやすい C++ 機能になります。

新しい構文を紹介する前に、短いコメントをしなければなりません。概念の調査と、制約のないプレースホルダーと制約のあるプレースホルダーを使用した実験の後、私は非常に偏っています。したがって、かなり客観的な投稿は期待できません。

永遠に繰り返される質問

C++ と Python のセミナーで、「いつプログラミング言語が簡単になるのか?」という質問をよく耳にします。もちろん、難しい問題を簡単な方法で解決できれば、プログラミング言語が簡単であるという答えはありません。それは矛盾です。

私にとって、プログラミング言語は、いくつかの単純な原則に還元できれば簡単です。私はそのような原則を赤い糸と呼んでいます .ドイツのことわざを理解していただければ幸いです。これらのいくつかの単純な原則のアイデアは、これらの原則から言語の機能を推測できるということです。私の定義によれば、Python は単純なプログラミング言語です。たとえば、シーケンスにスライスを作成するというアイデアを思いついた場合、この原則を多くのコンテキストに適用できます。

したがって、ちょうどその場で作成された範囲 range(0,10,3)、文字列、リスト、またはタプルの 3 番目の各要素を返したい場合、構文は同じ原則に従います。ちょうどその場で作成された範囲 range(9,0,-2) の 2 番目の要素、文字列、リスト、またはタプルを逆の順序で返す場合も、同じ原則が適用されます。

私の定義によれば、C++98 は単純な言語ではありません。 C++11 はその中間です。たとえば、中かっこですべてを初期化できるなどのルールがあります ({ } - 初期化を参照)。もちろん、C++14 でも単純な原則を見落としている機能がたくさんあります。私のお気に入りの 1 つは、一般化されたラムダ関数です。

1
2
3
4
5
6
auto genLambdaFunction= [](auto a, auto b) { return a < b; };

template <typename T, typename T2>
auto genFunction(T a, T2 b){
 return a < b;
}

パラメータ a と b にプレースホルダー auto を使用することで、一般化されたラムダ関数は魔法のように関数テンプレートになります。 (genLambdaFunction は、2 つの型パラメーターを受け入れるオーバーロードされた呼び出し演算子を持つ関数オブジェクトです。) genFunction も関数テンプレートですが、auto を使用して定義することはできません。したがって、さらに多くの構文を使用する必要があります (3 行目と 4 行目)。これは、多くの C++ プログラマにとって難しすぎる構文です。

まさにその非対称性は、プレースホルダー構文で削除されます。したがって、私たちには新しい単純な原則があり、私の定義によれば、C++ ははるかに使いやすくなります。

プレースホルダー

制約のないプレースホルダーと制約のあるプレースホルダーを取得します。 auto は制約のないプレースホルダーです。これは、自動定義された変数が任意の型になる可能性があるためです。概念は、その概念を満たす変数を定義するためにのみ使用できるため、制約付きのプレースホルダーです。 Haskell の型クラスの助けを借りて、概念の投稿で概念を紹介しました。私のアプローチは国際的に称賛と非難を受けました。

詳細を掘り下げる前に、簡単な概念を定義して使用しましょう。

シンプルなコンセプト

積分という概念のおかげで、私の gcd アルゴリズムの引数は積分でなければなりません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// conceptsIntegral.cpp

#include <type_traits>
#include <iostream>

template<typename T>
concept bool Integral(){
 return std::is_integral<T>::value;
}

template<typename T>
requires Integral<T>()
T gcd(T a, T b){
 if( b == 0 ){ return a; }
 else{
 return gcd(b, a % b);
 }
}

int main(){

 std::cout << std::endl;

 std::cout << "gcd(100, 10)= " << gcd(100, 10) << std::endl;
 std::cout << "gcd(100, 33)= " << gcd(100, 33) << std::endl;
 // std::cout << "gcd(5.5, 4,5)= " << gcd(5.5, 4.5) << std::endl;

 std::cout << std::endl;

}

6 行目で Integral という概念を定義しています。述語 std::is_integral::value が T に対して true を返す場合、概念 Integral は true と評価されます。 std::is_integral は型特性ライブラリの関数です。型特性ライブラリの関数を使用すると、コンパイル時に型をチェックできます。型特性ライブラリに関する投稿で型特性の詳細を読むことができます。特に、型特性ライブラリの関数を使用して、gcd アルゴリズムをますます型安全にしました。 12 行目でコンセプトを適用しました。次の投稿で、より簡単な方法でコンセプトを適用する方法を書きます。したがって、関数テンプレートと関数の間の境界は、連続して区別されます。

しかしここで、私の小さな例に戻ります。比較的新しい GCC 6.3 とコンパイラ フラグ -fconcepts のおかげで、プログラムをコンパイルして実行できます。

26 行目を使用するとどうなりますか?コンセプトが始まります。

もう一度、プレースホルダーに戻ります。具体的で、制約のある、および制約のないプレースホルダーです。

制約付きおよび制約なしのプレースホルダー

制約のないプレースホルダー (auto) を使用できる各状況で、制約付きのプレースホルダー (概念) を使用できます。 直感的なルールではない場合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// conceptsPlaceholder.cpp

#include <iostream>
#include <type_traits>
#include <vector>

template<typename T>
concept bool Integral(){
 return std::is_integral<T>::value;
}

Integral getIntegral(auto val){
 return val;
}

int main(){
 
 std::cout << std::boolalpha << std::endl;
 
 std::vector<int> myVec{1, 2, 3, 4, 5};
 for (Integral& i: myVec) std::cout << i << " ";
 std::cout << std::endl; 

 Integral b= true;
 std::cout << b << std::endl;
 
 Integral integ= getIntegral(10);
 std::cout << integ << std::endl;
 
 auto integ1= getIntegral(10);
 std::cout << integ1 << std::endl;
 
 std::cout << std::endl;

}

簡単にするために、7 行目から 10 行目で Integral の概念を再利用します。したがって、21 行目の範囲ベースの for ループで積分を反復し、24 行目の変数 b を整数にする必要があります。概念の使用は 27 行目と 30 行目に続きます。27 行目では、getIntegral(10) の戻り値の型が Integral の概念を満たす必要があることを要求しています。 30 行目はそれほど厳密ではありません。ここでは、制約のないプレースホルダーで問題ありません。

最後に、いつものように、プログラムの出力。驚きはありませんでした。概念は完全に直感的に動作します。

これで私の投稿は終わりです。 もちろん、違います! ほとんどの人は、私がプレースホルダーの新しい重要な機能を密かに導入したことを認識していなかったと思います. getIntegral 関数 (12 行目) をよく見てください。

Integral getIntegral(auto val){
 return val;
}

C++11 以降、制約のないプレースホルダーを戻り値の型として使用できるため、戻り値の型としての Integral という概念は非常に簡単に取得できます。 C++20 では、単純な規則に従って、制約付きのプレースホルダーを使用できます。私のポイントは別のものです。パラメータの型には auto を使用します。これは、一般化されたラムダ関数でのみ可能です (最初の例を参照)。一般化されたラムダ関数は、関数テンプレートの内部にあります。さて、赤い糸に戻ります。 getIntegral は auto パラメータにより関数テンプレートになります。 これは、通常の関数テンプレート構文なしで発生しています。 getIntegral は任意の型を受け入れ、Integral の概念を満たす型の値のみを返します。

次は?

次の投稿では、テンプレート、概念、およびプレースホルダーの統合が進んでいるため、プレースホルダーについての話を続けます。