web-dev-qa-db-ja.com

カスタマイズポイントオブジェクトとは何ですか?

C++標準の最後のドラフトでは、範囲ライブラリで広く使用されている、いわゆる「カスタマイズポイントオブジェクト」( [customization.point.object] )を紹介しています。

ADLが標準ライブラリで見つけたbeginswapdataなどのカスタムバージョンを作成する方法を提供していることを理解しているようです。あれは正しいですか?

これは、ユーザーがオーバーロードを定義する以前のプラクティスとどのように異なりますか。 begin自分の名前空間にある彼女のタイプは?特に、なぜobjectsなのか?

20
metalfox

カスタマイズポイントオブジェクトとは何ですか?

これらは、名前空間stdの関数オブジェクトインスタンスであり、2つの目的を満たします。first無条件でトリガーの(概念)型の要件を引数にトリガーしますその後、ネームスペースstdの適切な関数に、またはADL経由でディスパッチします。

特に、なぜobjectsなのか?

これは、ADLを介してユーザー提供の関数を直接取り込む2番目のルックアップフェーズを回避するために必要です(これは延期仕様によるものです)。詳細については、以下を参照してください。

...そしてそれらをどのように使用しますか?

アプリケーションを開発する場合:主に開発しません。これは標準のライブラリ機能であり、将来のカスタマイズポイントにコンセプトチェックを追加します。テンプレートのインスタンス化を台無しにすると、明確なエラーメッセージが表示されます。ただし、そのようなカスタマイズポイントへの修飾された呼び出しがあれば、それを直接使用できます。以下は、デザインに忠実な架空の_std::customization_point_オブジェクトの例です。

_namespace a {
    struct A {};
    // Knows what to do with the argument, but doesn't check type requirements:
    void customization_point(const A&);
}

// Does concept checking, then calls a::customization_point via ADL:
std::customization_point(a::A{});
_

これは現在、たとえば、 _std::swap_、_std::begin_など。

説明( N4381 の要約)

標準のこのセクションの背後にある提案を要約してみましょう。標準ライブラリで使用される「クラシック」カスタマイズポイントには2つの問題があります。

  • 彼らは間違いを犯しやすい。例として、汎用コードでオブジェクトを交換すると、次のようになるはずです。

    _template<class T> void f(T& t1, T& t2)
    {
        using std::swap;
        swap(t1, t2);
    }
    _

    しかし、代わりにstd::swap(t1, t2)への修飾された呼び出しを行うのは簡単すぎます。ユーザーが提供したswapが呼び出されることは決してありません( N4381 、モチベーションとスコープを参照)

  • さらに厳しく、そのようなユーザー提供の関数に渡される型の(概念化された)制約を集中化する方法はありません(これは、このトピックがC++ 20で重要になった理由でもあります)。再び N4381 から:

    _std::begin_の将来のバージョンでは、その引数モデルがRangeコンセプトを必要とするとします。このような制約を追加しても、_std::begin_を慣用的に使用するコードには影響しません。

    _using std::begin;_
    begin(a);

    beginの呼び出しがユーザー定義のオーバーロードにディスパッチする場合、_std::begin_の制約はバイパスされています。

提案で説明されているソリューションは、次のような架空の実装_std::begin_のようなアプローチによって両方の問題を軽減します。

_namespace std {
    namespace __detail {
        /* Classical definitions of function templates "begin" for
           raw arrays and ranges... */

        struct __begin_fn {
            /* Call operator template that performs concept checking and
             * invokes begin(arg). This is the heart of the technique.
             * Everyting from above is already in the __detail scope, but
             * ADL is triggered, too. */

        };
    }

    /* Thanks to @cpplearner for pointing out that the global
       function object will be an inline variable: */
    inline constexpr __detail::__begin_fn begin{}; 
}
_

最初に、たとえばstd::begin(someObject)は常に_std::__detail::__begin_fn_経由で迂回しますが、これは望ましいことです。不適格な呼び出しで何が起こるかについて、私は再び元の論文を参照します。

_std::begin_をスコープに組み込んだ後でbeginが修飾なしで呼び出された場合、状況は異なります。ルックアップの最初のフェーズでは、名前beginはグローバルオブジェクト_std::begin_に解決されます。ルックアップで関数ではなくオブジェクトが見つかったため、ルックアップの第2フェーズは実行されません。つまり、_std::begin_がオブジェクトの場合、using std::begin; begin(a);std::begin(a);と同等です。これは、すでに見てきたように、ユーザーの代わりに。

このように、std名前空間の関数オブジェクト内で概念チェックを実行できます。beforeユーザー提供の関数へのADL呼び出しが実行されます。これを回避する方法はありません。

17
lubgr

「カスタマイズポイントオブジェクト」は、少し間違っています。多く(おそらく過半数)は、実際にはカスタマイズポイントではありません。

ranges::beginranges::endranges::swapなどは「真の」CPOです。それらのいずれかを呼び出すと、呼び出すカスタマイズされたbeginまたはendまたはswapがあるかどうか、またはデフォルトの実装が使用するか、または代わりに(SFINAEに適した方法で)呼び出しの形式を変更する必要がある場合。多くのライブラリの概念はCPO呼び出しが有効であるという観点から定義されているため(RangeSwappableなど)、正しく制約された汎用コードはそのようなCPOを使用する必要があります。もちろん、具体的なタイプと、イテレータをそこから取り出す別の方法を知っている場合は、遠慮なくお問い合わせください。

ranges::cbeginのようなものは、「CP」部分のないCPOです。彼らは常にデフォルトのことをするので、それはカスタマイズのポイントではありません。同様に、範囲アダプターオブジェクトはCPOですが、それらについてカスタマイズ可能なものはありません。それらをCPOとして分類することは、一貫性の問題(cbeginの場合)または仕様の利便性(アダプター)の問題です。

最後に、ranges::all_ofのようなものは、準CPOまたはniebloidsです。それらは、代わりに関数オブジェクトとして実装できるように、特別な魔法のADLブロッキングプロパティとイタチ表現を備えた関数テンプレートとして指定されます。これは主に、std::rangesの制約付きアルゴリズムが修飾なしで呼び出されたときに、ADLが名前空間stdの制約なしのオーバーロードを取得しないようにするためです。 std::rangesアルゴリズムはイテレータとセンチネルのペアを受け入れるため、通常はstdの対応するものよりも専門性が低く、結果として過負荷の解決策が失われます。

8
T.C.