web-dev-qa-db-ja.com

Eric Nieblerのstd :: is_functionの実装はどのように機能しますか?

先週Eric Niebler tweetedstd::is_function 特性クラス:

#include <type_traits>

template<int I> struct priority_tag : priority_tag<I - 1> {};
template<> struct priority_tag<0> {};

// Function types here:
template<typename T>
char(&is_function_impl_(priority_tag<0>))[1];

// Array types here:
template<typename T, typename = decltype((*(T*)0)[0])>
char(&is_function_impl_(priority_tag<1>))[2];

// Anything that can be returned from a function here (including
// void and reference types):
template<typename T, typename = T(*)()>
char(&is_function_impl_(priority_tag<2>))[3];

// Classes and unions (including abstract types) here:
template<typename T, typename = int T::*>
char(&is_function_impl_(priority_tag<3>))[4];

template <typename T>
struct is_function
    : std::integral_constant<bool, sizeof(is_function_impl_<T>(priority_tag<3>{})) == 1>
{};

しかし、それはどのように機能しますか?

54
ComicSansMS

一般的な考え方

cpprefereence.comでのサンプル実装 のようなすべての有効な関数タイプをリストする代わりに、この実装はnot関数であるすべてのタイプをリストします。そして、それらのどれも一致しない場合にのみtrueに解決されます。

非関数型のリストは以下で構成されています(下から上):

  • クラスとユニオン(抽象型を含む)
  • 関数から返されるものすべて(voidおよび参照型を含む)
  • 配列型

これらの非関数型のいずれとも一致しない型は関数型です。 std::is_functionは、ラムダや関数呼び出し演算子を持つクラスなどの呼び出し可能な型をnotが関数であると明示的に考慮することに注意してください。

is_function_impl_

可能な非関数型ごとにis_function_impl関数のオーバーロードを1つ提供します。関数宣言は少し解析が難しい場合があるため、classesとunionsの場合の例を見てみましょう。

template<typename T, typename = int T::*>
char(&is_function_impl_(priority_tag<3>))[4];

この行は、is_function_impl_型の単一の引数を取り、4 charsの配列への参照を返す関数テンプレートpriority_tag<3>を宣言します。 Cの古代から慣習であるように、宣言構文は配列型の存在によって恐ろしく複雑になります。

この関数テンプレートは、2つのテンプレート引数を取ります。最初は制約のないTですが、2番目はT型のintのメンバーへのポインターです。ここのint部分は実際には重要ではありません。これは、T型のメンバーを持たないintsでも機能します。ただし、クラスまたはユニオン型ではないTsの構文エラーが発生します。これらの他のタイプの場合、関数テンプレートをインスタンス化しようとすると、置換エラーが発生します。

priority_tag<2>およびpriority_tag<1>オーバーロードにも同様のトリックが使用されます。これらは、2番目のテンプレート引数を使用して、有効な関数戻り型または配列型であるTsのみをコンパイルする式を形成します。 priority_tag<0>オーバーロードのみが、このような制約のある2番目のテンプレートパラメーターを持たないため、任意のTでインスタンス化できます。

全体として、is_function_impl_の4つの異なるオーバーロードを宣言します。これらは、入力引数と戻り値の型によって異なります。それぞれが異なるpriority_tag型を引数として取り、異なる一意のサイズのchar配列への参照を返します。

is_functionでのタグのディスパッチ

現在、is_functionをインスタンス化するとき、Tis_function_implをインスタンス化します。この関数に4つの異なるオーバーロードを提供したため、ここでオーバーロードの解決を行う必要があることに注意してください。そして、これらのオーバーロードはすべて関数templatesであるため、これは [〜#〜] sfinae [〜#〜] が起動する可能性があることを意味します。

したがって、関数(および関数のみ)の場合、priority_tag<0>を持つ最も一般的なものを除き、すべてのオーバーロードが失敗します。それで、最も一般的なものである場合、インスタンス化が常にそのオーバーロードに解決しないのはなぜですか?オーバーロードされた関数の入力引数のため。

priority_tagは、priority_tag<N+1>priority_tag<N>をパブリックに継承するように構築されていることに注意してください。ここで、is_function_implpriority_tag<3>で呼び出されるため、そのオーバーロードは、オーバーロード解決のために他のものよりもbetter matchであるため、最初に試されます。置換エラーが原因で失敗した場合にのみ、次善の一致が試行されます。これはpriority_tag<2>オーバーロードです。インスタンス化できるオーバーロードが見つかるか、制約されず常に機能するpriority_tag<0>に到達するまで、この方法を続けます。非関数型はすべて、より高いprioオーバーロードでカバーされているため、これは関数型でのみ発生します。

結果の評価

結果を評価するために、is_function_impl_の呼び出しによって返された型のサイズを調べます。各オーバーロードは、異なるサイズのchar配列への参照を返すことに注意してください。したがって、sizeofを使用して、どのオーバーロードが選択されたかを確認し、priority_tag<0>オーバーロードに達した場合にのみ結果をtrueに設定できます。

既知のバグ

Johannes Schaub バグを発見 実装。不完全なクラス型の配列は、誤って関数として分類されます。これは、配列型の現在の検出メカニズムが不完全な型では機能しないためです。

51
ComicSansMS