C++20:2 つの極限と概念による救済

前回の投稿で C++20 の概要を説明しました。では、詳細を見ていきましょう。概念よりも優れた旅の出発点は何でしょうか?

告白しなければならないのは、私は概念の大ファンであり、したがって偏見があるということです。とにかく、やる気を起こさせる例から始めましょう。

2 つの極限

C++20 まで、C++ には、関数またはクラスについて考えるための 2 つの正反対の方法がありました。関数またはクラスは、特定の型またはジェネリック型で定義できます。 2 番目のケースでは、それらを関数またはクラス テンプレートに呼び出します。それぞれの方法の何が問題になっていますか?

具体的すぎる

特定の型ごとに関数やクラスを定義するのは大変な作業です。その負担を避けるために、型変換が助けになることがよくあります。救助のように見えることは、しばしば呪いです.

// tooSpecific.cpp

#include <iostream>

void needInt(int i){
 std::cout << "int: " << i << std::endl;
}

int main(){
 
 std::cout << std::boolalpha << std::endl;
 
 double d{1.234}; // (1)N
 std::cout << "double: " << d << std::endl;
 needInt(d); // (2) 
 
 std::cout << std::endl;
 
 bool b{true}; // (3)
 std::cout << "bool: " << b << std::endl;
 needInt(b); // (4)
 
 std::cout << std::endl;
 
}

最初のケース (1 行目) では、double で開始し、int で終了します (2 行目)。 2 番目のケースでは、bool で開始し (3 行目)、int で終了します (4 行目)。

ナローイング コンバージョン

getInt(int a) を呼び出しています double g で あなたはコンバージョンを狭めています。絞り込み変換とは、正確さが失われる変換です。これはあなたが望むものではないと思います。

インテグラル プロモーション

しかし、その逆も良くありません。 bool で getInt(int a) を呼び出すと、bool が int に昇格します。驚いた?多くの C++ 開発者は、bool に追加するときにどの型を取得するかを知りません。

template <typename T>
auto add(T first, T second){
 return first + second;
}

int main(){
 add(true, false);
}

C++ Insights が真実を示します。

関数テンプレート add のテンプレートのインスタンス化により、戻り値の型が int の完全な特殊化 (6 行目から 12 行目) が作成されます。

関数が特定の型のみを受け入れるという事実に対処するには、便利な理由から C/C++ の変換の魔法全体が必要であるというのが私の強い信念です。

わかった。逆にやってみましょう。具体的に書くのではなく、一般的に書きます。おそらく、テンプレートを使用して一般的なコードを書くことが私たちの救いです.

一般的すぎる

これが私の最初の試みです。並べ替えは、そのような一般的なアイデアです。コンテナの要素がソート可能な場合、コンテナごとに機能するはずです。 std::list に std::sort を適用しましょう。

// sortList.cpp

#include <algorithm>
#include <list>

int main(){
 
 std::list<int> myList{1, 10, 3, 2, 5};
 
 std::sort(myList.begin(), myList.end());
 
}

おお!これは、小さなプログラムをコンパイルしようとしたときに得られるものです。

このメッセージを解読したくありません。何がうまくいかないのですか?使用されている std::sort のオーバーロードのシグネチャを詳しく見てみましょう。

template< class RandomIt >
void sort( RandomIt first, RandomIt last );

std::sort は、RandomIT などの奇妙な名前の引数を使用します。 RandomIT は、ランダム アクセス イテレータの略です。これが、テンプレートが悪名高い圧倒的なエラー メッセージの理由です。 std::list は双方向イテレータのみを提供しますが、std:sort はランダム アクセス イテレータを必要とします。 std::list の構造を見れば、これは明らかです。

cppreference.com ページの std::sort に関するドキュメントを注意深く調べると、非常に興味深いことがわかります:std::sort の型要件です。

救助へのコンセプト

概念は、テンプレート パラメーターにセマンティックな制約を課すため、助けになります。

std::sort に関する既に述べた型要件は次のとおりです。

  • RandomIt ValueSwappable と LegacyRandomAccessIterator の要件を満たす必要があります。
  • 逆参照された RandomIt の型 MoveAssignable と MoveConstructible の要件を満たす必要があります。
  • Compare Compare の要件を満たす必要があります。

std::sort の型要件は概念です。概念の簡単な紹介については、私の投稿 C++20:The Big Four をお読みください。特に、std::sort には LegacyRandomAccessIterator が必要です。コンセプトを詳しく見ていきましょう。 cppreference.com の例を少し洗練させました。

template<typename It>
concept LegacyRandomAccessIterator =
 LegacyBidirectionalIterator<It> && // (1)
 std::totally_ordered<It> &&
 requires(It i, typename std::incrementable_traits<It>::difference_type n) {
 { i += n } -> std::same_as<It&>; // (2)
 { i -= n } -> std::same_as<It&>;
 { i + n } -> std::same_as<It>;
 { n + i } -> std::same_as<It>;
 { i - n } -> std::same_as<It>;
 { i - i } -> std::same_as<decltype(n)>;
 { i[n] } -> std::convertible_to<std::iter_reference_t<It>>;
 };

これが重要な観察です。型 LegacyBidirectionalIterator の概念 (2 行目) とその他すべての要件をサポートする場合、LegacyRandomAccessIterator の概念をサポートします。たとえば、2 行目の要件は、It 型の値の場合、{ i +=n } が有効な式であり、I&を返すことを意味します。私の話を完成させるために、std::list は LegacyBidirectionalIterator をサポートします。

確かに、このセクションは非常に技術的でした。試してみましょう。概念では、次のような簡潔なエラー メッセージが期待できます:

もちろん、このエラー メッセージは偽物でした。概念の C++20 構文を実装するコンパイラは存在しないためです。 MSVC 19.23 はそれらを部分的にサポートし、GCC は概念の以前のバージョンをサポートします。 cppreference.com では、概念の現在の状態について詳しく説明しています。

GCC が以前のバージョンの概念をサポートしていることは言及しましたか?

長い長い歴史

2005 年から 2006 年頃に概念について初めて耳にしました。Haskell の型クラスを思い出しました。 Haskell の型クラスは、同様の型のインターフェイスです。 Haskell 型クラス階層の一部です。

しかし、C++ の概念は異なります。ここにいくつかの観察があります。

  • Haskell では、型は型クラスのインスタンスでなければなりません。 C++20 では、型は概念の要件を満たす必要があります。
  • コンセプトは、テンプレートの非型引数で使用できます。たとえば、5 などの数値は非型引数です。 5 つの要素を持つ int の std::array が必要な場合は、非型引数 5 を使用します:std::array myArray.
  • コンセプトはランタイム コストを追加しません。

本来、概念は C++11 の重要な機能であるべきです 、しかし、2009 年 7 月にフランクフルトで開催された標準化会議で削除されました。 Bjarne Stroustrup からの引用は、次のように語っています。 ". 数年後、次の試みも成功しませんでした:コンセプト ライトは C++17 から削除されました 標準。最後に、これらは C++20 の一部です。

次は?

もちろん、次の投稿はコンセプトについてです。テンプレート パラメーターのセマンティック制約が意味する多くの例を紹介します。