13

C++ の概念で正確に新しいものは何ですか? 私の理解では、それらは を使用することと機能的に同等static_assertですが、「良い」方法でコンパイラエラーが読みやすくなります (Bjarne Stroustup が言ったように、10 ページまたはエラーは得られず、1 つだけです)。

基本的に、コンセプトでできることはすべて を使っても実現できるというのは本当static_assertですか?

足りないものはありますか?

4

1 に答える 1

23

tl;dr

と比較してstatic_assert、概念は次の理由でより強力です。

  • 彼らはあなたが簡単に達成できない優れた診断を提供しますstatic_asserts
  • なしでテンプレート関数を簡単にオーバーロードできますstd::enable_if(これは のみでは不可能ですstatic_asserts)
  • 静的インターフェイスを定義し、診断を失うことなくそれらを再利用できます (static_asserts各関数に複数の必要があります)。
  • 意図をより適切に表現し、読みやすさを向上させます (これはテンプレートの大きな問題です)。

これにより、次の世界が楽になります。

  • テンプレート
  • 静的多型
  • 過負荷

興味深いパラダイムの構成要素となります。


コンセプトとは?

概念は、特定の要件を満たす型の "クラス" (C++ 用語ではなく "グループ" として) を表現します。Swappable例として、概念が次のタイプのセットを表現していることがわかります。

  • への呼び出しを許可しますstd::swap

また、たとえば、std::stringstd::vector、などはこの要件を満たしているため、次のような関数で互換的に使用できることが簡単にわかります。std::dequeint

template<typename Swappable>
void func(const Swappable& a, const Swappable& b) {
    std::swap(a, b);
}

概念は常に C++に存在していました。(おそらく近い将来) に追加される実際の機能では、言語でそれらを表現して強制することができます。


より良い診断

より良い診断に関する限り、当面は委員会を信頼する必要があります. しかし、彼らが「保証する」出力:

error: no matching function for call to 'sort(list<int>&)'
sort(l); 
      ^
note: template constraints not satisfied because 
note: `T' is not a/an `Sortable' type [with T = list<int>] since
note: `declval<T>()[n]' is not valid syntax

非常に有望です。

sを使用して同様の出力を実現できることは事実ですが、関数ごとstatic_assertに異なる s が必要static_assertになり、非常に速く退屈になる可能性があります。

Container例として、テンプレート パラメーターを受け取る 2 つの関数の概念によって与えられた要件の量を適用する必要があるとします。両方の関数でそれらを複製する必要があります。

template<typename C>
void func_a(...) {
    static_assert(...);
    static_assert(...);
    // ...
}

template<typename C>
void func_b(...) {
    static_assert(...);
    static_assert(...);
    // ...
}

そうしないと、どの要件が満たされていないかを区別する能力が失われます。

代わりにコンセプトを使用すると、次のように書くだけで、コンセプトを定義して適用できます。

template<Container C>
void func_a(...);

template<Container C>
void func_b(...);

概念のオーバーロード

導入されたもう 1 つの優れた機能は、テンプレート制約でテンプレート関数をオーバーロードする機能です。はい、これも で可能ですがstd::enable_if、それがどれほど醜くなるかは誰もが知っています。

例として、s で動作する関数を作成し、たまたまsContainerでより適切に動作するバージョンでオーバーロードすることができます。SequenceContainer

template<Container C>
int func(C& c);

template<SequenceContainer C>
int func(C& c);

概念のない代替案は次のようになります。

template<typename T>
std::enable_if<
    Container<T>::value,
    int
> func(T& c);

template<typename T>
std::enable_if<
    SequenceContainer<T>::value,
    int
> func(T& c);

間違いなく醜く、おそらくエラーが発生しやすくなります。


よりクリーンな構文

上記の例で見たように、構文は間違いなく明確で、概念がより直感的です。これにより、制約を表現するために必要なコードの量を減らし、読みやすさを向上させることができます。

前に見たように、実際には次のようなもので許容レベルに達することができます:

static_assert(Concept<T>::value);

しかし、その時点で、さまざまなstatic_assert. 概念を使用すると、このトレードオフは必要ありません。


静的多型

そして最後に、概念は、Haskell の型クラスのような他の機能パラダイムと興味深い類似点を持っています。たとえば、静的インターフェイスを定義するために使用できます。

たとえば、(悪名高い) ゲーム オブジェクト インターフェイスの古典的なアプローチを考えてみましょう。

struct Object {
    // …
    virtual update() = 0;
    virtual draw() = 0;
    virtual ~Object();
};

次に、派生オブジェクトのポリモーフィックがあると仮定すると、std::vector次のことができます。

for (auto& o : objects) { 
    o.update();
    o.draw();
}

すばらしいことですが、複数の継承またはエンティティ コンポーネント ベースのシステムを使用したくない場合は、クラスごとに可能なインターフェイスが 1 つしかないことにほとんど悩まされます。

しかし、実際に静的ポリモーフィズム(それほど動的ではないポリモーフィズム)が必要な場合は、メンバー関数 (および場合によっては他の関数)を必要とするObject概念を定義できます。updatedraw

その時点で、無料の関数を作成できます。

template<Object O>
void process(O& o) {
    o.update();
    o.draw();
}

その後、他の要件を持つゲーム オブジェクトの別のインターフェイスを定義できます。このアプローチの優れた点は、必要なだけ多くのインターフェイスを開発できることです。

  • クラスを変更する
  • 基本クラスが必要

そして、それらはすべてコンパイル時にチェックされ、適用されます。

これは単なるばかげた例 (そして非常に単純化された例) ですが、概念によって、C++ のテンプレートのまったく新しい世界が開かれます。

さらに詳しい情報が必要な場合は、C++ の概念と Haskell 型クラスに関するこの素晴らしい記事を読むことができます。

于 2014-04-15T22:12:13.987 に答える