先週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>
{};
しかし、それはどのように機能しますか?
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 char
sの配列への参照を返す関数テンプレートpriority_tag<3>
を宣言します。 Cの古代から慣習であるように、宣言構文は配列型の存在によって恐ろしく複雑になります。
この関数テンプレートは、2つのテンプレート引数を取ります。最初は制約のないT
ですが、2番目はT
型のint
のメンバーへのポインターです。ここのint
部分は実際には重要ではありません。これは、T
型のメンバーを持たないint
sでも機能します。ただし、クラスまたはユニオン型ではないT
sの構文エラーが発生します。これらの他のタイプの場合、関数テンプレートをインスタンス化しようとすると、置換エラーが発生します。
priority_tag<2>
およびpriority_tag<1>
オーバーロードにも同様のトリックが使用されます。これらは、2番目のテンプレート引数を使用して、有効な関数戻り型または配列型であるT
sのみをコンパイルする式を形成します。 priority_tag<0>
オーバーロードのみが、このような制約のある2番目のテンプレートパラメーターを持たないため、任意のT
でインスタンス化できます。
全体として、is_function_impl_
の4つの異なるオーバーロードを宣言します。これらは、入力引数と戻り値の型によって異なります。それぞれが異なるpriority_tag
型を引数として取り、異なる一意のサイズのchar配列への参照を返します。
is_function
でのタグのディスパッチ現在、is_function
をインスタンス化するとき、T
でis_function_impl
をインスタンス化します。この関数に4つの異なるオーバーロードを提供したため、ここでオーバーロードの解決を行う必要があることに注意してください。そして、これらのオーバーロードはすべて関数templatesであるため、これは [〜#〜] sfinae [〜#〜] が起動する可能性があることを意味します。
したがって、関数(および関数のみ)の場合、priority_tag<0>
を持つ最も一般的なものを除き、すべてのオーバーロードが失敗します。それで、最も一般的なものである場合、インスタンス化が常にそのオーバーロードに解決しないのはなぜですか?オーバーロードされた関数の入力引数のため。
priority_tag
は、priority_tag<N+1>
がpriority_tag<N>
をパブリックに継承するように構築されていることに注意してください。ここで、is_function_impl
がpriority_tag<3>
で呼び出されるため、そのオーバーロードは、オーバーロード解決のために他のものよりもbetter matchであるため、最初に試されます。置換エラーが原因で失敗した場合にのみ、次善の一致が試行されます。これはpriority_tag<2>
オーバーロードです。インスタンス化できるオーバーロードが見つかるか、制約されず常に機能するpriority_tag<0>
に到達するまで、この方法を続けます。非関数型はすべて、より高いprioオーバーロードでカバーされているため、これは関数型でのみ発生します。
結果を評価するために、is_function_impl_
の呼び出しによって返された型のサイズを調べます。各オーバーロードは、異なるサイズのchar配列への参照を返すことに注意してください。したがって、sizeof
を使用して、どのオーバーロードが選択されたかを確認し、priority_tag<0>
オーバーロードに達した場合にのみ結果をtrue
に設定できます。
Johannes Schaub バグを発見 実装。不完全なクラス型の配列は、誤って関数として分類されます。これは、配列型の現在の検出メカニズムが不完全な型では機能しないためです。