テンプレートマジック、メタ関数IsValidExpression

こんにちは、Habrasociety様。

今日は、特定の表現のコンパイル可能性を判断できる興味深いトリックを紹介します。

例:
/ * HasFメタ関数を定義します。これにより、クラスのf()関数の存在を判別できます。 * /
DECLARE_IS_VALID_EXPRESSION
HasF、
U * NULL - > f / *この式は、U :: f()* / )が 存在する場合にのみコンパイルされ ます。

struct Foo { void f ; } ;
struct Bar { } ;

BOOST_STATIC_ASSERT HasF <A> :: ; / *ここで、定数HasF <A> ::値はtrueになります* /
BOOST_STATIC_ASSERT HasF < B > :: value ; / *ここで、定数HasF <A> ::値はfalseになります* /

おそらく既に推測されているように、マクロDECLARE_IS_VALID_EXPRESSIONの書き方を考えます。

したがって、私たちの目標は、式がコンパイルされているかどうかを判断することを学ぶことです。 この場合、コンパイラはもちろんエラーを発生させてはなりません。式がコンパイルされていない場合は値0、それ以外の場合は値1を使用して定数を生成するだけです。

リリース


このために、SFINAEの原則を使用します(置換の失敗はエラーではありません)。 人間の言語では、これは、コンパイラーがテンプレート定義内(テンプレートの本体ではなく定義内、つまりコンパイラーがコードに適したテンプレートパラメーターを「選択」しようとする場所)で「エラー」に遭遇した場合、この「エラー」 「コンパイルエラーではなく、「エラー」の原因となるパラメーターを使用してテンプレート関数(またはクラス)をインスタンス化しようとする試みの終了につながります。」

これが、次のコードの仕組みです。
$ define DECLARE_IS_VALID_EXPRESSION NAME、U_BASED_RUNTIME_EXPRESSION \
テンプレート < クラス T > \
構造体 NAME \
{ \
/ *比較のために間違いなくTではない型が必要* / \
struct CDummy { } ; \
\
/ *このオーバーロードは、U_BASED_RUNTIME_EXPRESSIONに「エラー」が含まれていない場合にのみ機能します\
**それ以外の場合、このオーバーロードはSFINAEに従って無視されます。 * / \
テンプレート < タイプ名 U > \
静的な decltype U_BASED_RUNTIME_EXPRESSION F void * ; \
\
/ *ただし、このオーバーロードは常に存在しますが、省略記号が優先されるため、優先順位は低くなります* / \
テンプレート < タイプ名 U > \
静的 CDummy F ... ; \
\
/ *このtypedefは存在しないかもしれませんが、それがないとこのクラスは正しく動作しません:( \
**(この機会に、Visual Studioチームのテスターに​​挨拶を送信します)* / \
typedef decltype F < T > nullptr \
TDummy \
\
列挙型 \
{ \
/ * U_BASED_RUNTIME_EXPRESSIONに「エラー」が含まれていない場合、値は1であり、それ以外の場合は0です\
**なぜですか? \
**「エラー」がない場合、Fの両方のバージョンが存在し、F <T>(nullptr)はそれを選択します、\
**楕円がない場合、つまり テスト式で、その戻り値の型は
** CDummyではありません。なぜなら CDummyはローカルで宣言されます。 \
**「エラー」がある場合、テストされた式を持つオプションFがスローされ、\
**したがって、F <T>(nullptr)は2番目のオーバーロード(CDummyを返す)を選択します* / \
= boost :: is_same < CDummy、TDummy > :: \
} ; \
} ;

残念ながら、この実装にはC ++ 0xが必要です(私のコンパイラはVC10です)。 理論的には、新しい標準がなくても可能です(考え方は同じですが、decltypeの代わりにsizeofが使用されます)。 しかし! ここでも、マイクロソフトのテスターに​​挨拶したいと思います。 sizeofはテンプレート定義エリアで正しく機能しません-そこに「期待」されていません(正しく覚えていれば)。 gccでは、sizeofのソリューションは正常に機能します。

申込み


たとえば、次のコードは例として使用できます。
/ *返されるメタ関数IsStreamSerializationSupportedを定義します
**引数がストリームを介した入力/出力をサポートする場合はtrue * /
DECLARE_IS_VALID_EXPRESSION
IsStreamSerializationSupported、
std :: cout << * U * NULL std :: cin >> * U * NULL ;

/ * doubleは、「すぐに使える」ストリームを介した入出力をサポートします* /
BOOST_STATIC_ASSERT IsStreamSerializationSupported < double > :: value ;

struct Foo { } ;

/ *ただし、ストリームを介したFooの入力/出力は、:( * /
BOOST_STATIC_ASSERT IsStreamSerializationSupported < Foo > :: value ;

struct Bar { } ;

テンプレート < クラス TChar、 クラス Traits >
std :: basic_ostream < TChar、Traits > 演算子<<
std :: basic_ostream < TChar、Traits > const Bar ;

テンプレート < クラス TChar、 クラス Traits >
std :: basic_istream < TChar、Traits > 演算子>>
std :: basic_istream < TChar、Traits > 、Bar ;

/ *バーは、ストリームを介したI / Oをサポートします。 対応する演算子が定義されています。 * /
BOOST_STATIC_ASSERT IsStreamSerializationSupported < Bar > :: value ;

このようなことは、テンプレートに渡された型がさまざまな概念と一致することを確認するのに役立ちます(この場合、型は概念と一致するためにストリームを介した入出力をサポートする必要があります)。

これについては、さようならを言います、あなたのすべての注意に感謝します! :)

Source: https://habr.com/ru/post/J112239/


All Articles