C ++ 98/03のis_function メタ関数の別の実装

これは、C ++ 98/03のis_functionメタ関数をどのように蚘述したかに぀いおの小さなレポヌトです。そのため、異なる数の匕数に察しお倚くの特殊化を䜜成する必芁はありたせん。
2016幎にそのようなこずをするのはなぜですか 答えたす。 これは挑戊です。 ずりわけ、これは、「可胜かどうか」ずいうカテゎリヌからの玔粋に理論的な最初の研究であり、珟代のコンパむラヌの問題を明らかにしたした。 この気分をぜひご芧ください。

ステヌゞング
理論のビット
実装番号0
実斜番号1
実装番号2
結論の代わりに

ステヌゞング


is_function<T>は、タむプTが「関数」のタむプでtrue堎合はtrue返し、 trueない堎合はtrue返しtrue 。 そのため、調査䞭の関数型のさたざたな数の匕数型に察しお倚数の特殊化を䜿甚せずにメタ関数を蚘述する必芁がありたす。 boostに特化した実装䟋を芋るこずができたす。 珟時点では、最倧25個の匕数があり、このすべおに倚くのスペヌスが必芁です。
なぜ専門化するのですか C ++ 11では、すばらしいツヌルが登堎したした- 可倉長テンプレヌト 。 それは熟しおいたために珟れた、ず蚀われるかもしれたせんが、ペント。 このツヌルを䜿甚するず、テンプレヌト匕数のこれらのシヌケンスを凊理できたす
  some_template<A1, A2, A3 /*, etc. */> 
パラメヌタの単䞀の「パッケヌゞ」、パラメヌタパックずしお。 パラメヌタパックは、匕数の数が䞍明な堎合に、䞀般化された特殊化、オヌバヌロヌド、および眮換を行うのに圹立ちたす。 is_functionがC ++ 11で実装されるのはこのツヌルです。 C ++ 98/03では、そのようなツヌルはありたせんでした。 ぀たり、䞀般的なケヌスでは、状況に応じお異なる数の匕数を提䟛する必芁がある堎合、「すべおの堎合」にオヌバヌロヌドず特殊化を行う必芁がありたした。 boostのvariantやmplなどのラむブラリの実装を芋る堎合、そのようなコヌドが豊富であるこずを確認しおくださいプリプロセッサによっお生成されるこずもありたす。 タむプT R(A1, A2)関数でTかどうかを刀断する必芁がある堎合、最も簡単で明癜な解決策は、察応する特殊化を䜜成するこずでした。
  template <typename F> struct is_function { /* .... */ }; template <typename R, typename A1, typename A2> struct is_function<R(A1, A2)> { /* .... */ }; 
倚くの堎合、異なる方法でそれを行うこずは単に䞍可胜でした。 ブヌストでの実装に䞍満があるずは思わないでください-それらの゜リュヌションは最も移怍性が高く、したがっお、特にそのようなラむブラリのコンテキストで最も正確です。 しかし、手近な仕事でこれなしでやるこずは私にずっお興味深いこずでした。
䞀般的に、私はサヌビスのおかげで䞍幞な人の䞀人ですこれは芋た目ですがが、私はただC ++ 03で䜜業しおいたす。 したがっお、叀いコヌドずの互換性に関する私の懞念は、驚くこずではありたせん。 誰かが蚀うかもしれたせん「はい、このゞャンクはあなたに䞎えられたした、2016幎の庭で」。 これに同意するこずはできたすが、玔粋に䞻芳的な競争感芚に加えお、これからいく぀かの利点を匕き出すこずが刀明したした。 そしお、C ++ 03の粟神は、䞊蚘の理由により、ただ颚化するこずができたせんでした。 したがっお、楜しみのためだけに。
説明に移る前に、読者がSFINAEずは䜕か、コンパむル時チェックを蚘述する基本原則を理解する必芁があるこずを譊告したいず思いたす。暙準からの衚珟を正確に翻蚳したせん。 興味のある読者は、望むなら、自分でこれに察凊できるず思いたす。

理論のビット


特殊化を䌎う叀兞的なアプロヌチが私たちに合わない堎合、それでは䜕ですか 少し違った考え方をしおみたしょう。 他にはない関数型のプロパティは䜕ですか 暙準C ++ 03を芋おみたしょう。
4.3 / 1
関数型Tの巊蟺倀は、型「Tぞのポむンタヌ」の右蟺倀に倉換できたす 。結果は、関数ぞのポむンタヌです。
そのため、関数は暗黙的に関数ぞのポむンタヌに倉換できたす。 配列には同じプロパティがありたす。配列は暗黙的にポむンタに倉換されたす。 他に䜕があるか芋おみたしょう
8.3.5 / 3
各パラメヌタヌの型を決定した埌、「Tの配列」たたは「Tを返す関数 」 型のパラメヌタヌは 、それぞれ「Tぞのポむンタヌ 」たたは「Tを返す関数ぞのポむンタヌ 」に調敎されたす 。
これは、「関数」タむプが別の関数のパラメヌタヌずしお指定された堎合、暗黙的にポむンタヌのプロパティを取埗するこずを意味したす。 これに基づいお、パラグラフ13.1では、そのような宣蚀は
  void foo(int ()); 
これに察応したす぀たり、たったく同じです
  void foo(int (*)()); 
これはすでに䜿甚できたす。 これに基づいおチェックを蚘述し、目の前の機胜を刀断できたす。 ただし、すべおは芋た目ほど単玔ではありたせんが、埌でさらに詳しく説明したす。 それたでの間、「関数」のタむプに基づいお、他に䜿甚できるものを芋おみたしょう。
8.3.4 / 1
Dが次の圢匏である宣蚀TD
D1 [定数匏opt ]
宣蚀T D1の識別子の型は「掟生宣蚀子型リストT」であり、Dの識別子の型は配列型です。 Tは配列芁玠型ず呌ばれたす。 この型は、参照型、おそらくcv修食された型void、関数型、たたは抜象クラス型であっおはなりたせん。
ええ、それも面癜いです。 ぀たり 「関数」型の芁玠の配列を取埗するこずはできたせん。 これに加えお、リンクの配列、 void配列、および抜象クラスの型の芁玠を持぀配列を取埗するこずはできたせん。 残りのオプションを遮断するチェックを蚘述するず、目の前の機胜を正確に刀断できたす。
たずめたす。 関数には2぀の特城がありたす。 タむプ「機胜」
これらのプロパティを蚈画の実装に䜿甚したす。

実装番号0、予備


前のセクションで特定した「関数」タむプの最初の機胜から始めたいず思いたす。 箄8.3.5 / 3です。
次のように芁玄できたす。チェックされた型Fは、等匏の堎合に関数です。
void( F ) == void( F * ) 。
それはすべお非垞に簡単に聞こえたす。 たた、最初の実装も非垞に簡単でした。 シンプルだが間違っおいる。 このため、党䜓を説明するこずはしたせんが、その䞭で䜿甚した1぀のプロパティに぀いお個別に話したいず思いたす。 このコヌドを怜蚎しおください。
  template <typename F> static void (* declfunc() )( F ); template <typename F> static void (* gen( void (F *) ) )( F ); template <typename F> static void (* gen( void (F ) ) )( F * ); 
将来を芋据えお、このコヌドは誀った仮定に由来するず蚀うでしょう。 しかし、Clangコンパむラヌバヌゞョン3.4たで、GCCコンパむラヌバヌゞョン4.9たで、VSのコンパむラヌcl 19.x、おそらく以前は、予想どおりにコンパむルしたした。 どのように機胜し、どのように䜿甚する予定だったかを説明したす。 たず、チェックに圹立぀関数宣蚀を䜜成したす。
  template <typename X> static char (& check_is_function( X ) ) [ is_same<void(*)( F ), X>::value + 1 ]; 
check_is_functionに枡される型がvoid(*)( F )に䞀臎する堎合、関数は2぀のchar配列ぞの参照を返したす。䞀臎しない堎合は、1぀のcharから掟生しvoid(*)( F ) sizeofを䜿甚しお戻り倀の型を分析できたす。 ここでは、 Fがタむプ「関数」に属するこずを調査䞭のタむプであるこずを受け入れたす。 さお、これをシンプルなテンプレヌトに入れるず、
  template <typename F> struct is_function { template <typename X> static char (& check_is_function( X ) ) [ is_same<void(*)( F ), X>::value + 1 ]; enum { value = sizeof( check_is_function( gen( declfunc<F>() ) ) ) - 1 }; }; 
䞊蚘のコンパむラで次の圢匏の匏を確認できたす。
  is_function<int()>::value; is_function<int>::value; typedef void fcv() const; is_function<fcv>::value; 
結果1、0、および1をそれぞれ取埗したす完党なコヌドはここにあり、 ここで実行できたす 。 はい、これは完党に機胜する゜リュヌションではありたせん。ここでは、関数ポむンタず関数を区別しおいたせん。リンク、 voidなどに問題がありたす。 しかし、これはすべおバむパスされおいるので、これに泚意を集䞭したくありたせん。 指定したコンパむラGCC> = 4.9、Clang> = 3.5、cl 19.xよりも新しいコンパむラで同じ䟋を実行する堎合は、出力が倉曎されおいるこずを確認しおください。 結果1、0、0をそれぞれ取埗したす。 これは、 cv-qualifier-seq これは最埌に同じconstたたはvolatile を持぀関数の型別の関数の型でポむンタヌプロパティを取埗䞭に眮換される、バリアントの有効な匕数眮換でなくなったために発生したす。
  template <typename F> static void (* gen( void (F *) ) )( F ); 
なんで 最終ドラフトず明確に述べられおいる新しい暙準の導入により、
8.3.1 / 4
関数型にcv修食子たたはref修食子がある堎合、関数型ぞのポむンタヌの圢成は䞍正です。
同様のコヌドに察するコンパむラのアプロヌチが倉曎されたした。 このタむプにアスタリスクを远加するのは間違った眮換であるため、コヌドは機胜しなくなりたした。 C ++ 03では、同様に明確なルヌルはありたせんでした倉曎に぀いおは、 こちらを参照しおください 。 もちろん、それはそこで蚱可されおいたずいう意味ではありたせん。 ただし、暙準の文蚀の曖昧さにより、この点をスキップする機䌚が残りたした。これは以䞋のリンクに蚘茉されおいるものです。
cv-qualifiersたたはref-qualifiersを含む関数型ぞのポむンタヌず参照が蚱可されおいないため、テンプレヌト匕数の眮換䞭に䜜成された堎合、掚論に倱敗するこずを既存の文蚀から十分に明らかにしおいたせん
したがっお、倚くの最新のコンパむラヌは、これをただ考慮しおいたせんたずえば、cl 18.xたたはicc 13.xおよび14.x。 「 cv-qualifier-seqを䜿甚した関数」の型にアスタリスクを明瀺的に远加するこずが蚱可されない堎合、この型をパラメヌタヌずしお指定する際に暗黙的なものも䜿甚できないはずだず、すでに泚意深い読者はおそらく疑問に思っおいたす。 はい、おそらくそうです。 ただし、珟時点では、明瀺的に犁止する単䞀のコンパむラはありたせん。
C ++ 03暙準には次のものがありたす。
8.3.5 / 4
cv-qualifier-seqは、非静的メンバヌ関数の関数型、メンバヌぞのポむンタヌが参照する関数型、たたは関数typedef宣蚀の最䞊䜍関数型の䞀郚にのみなりたす。
これは、 cv-qualifier-seqで関数型を䜿甚するかなり狭いコンテキストに぀いお教えおくれたす。 そしお、私たちのケヌスはそこに収たらないようです。
したがっお、関数型のポむンタヌぞの「倉異」に基づいお構築されたコヌドはすべおの堎合に機胜するわけではありたせんが、珟時点ではただ機胜するため、完党な゜リュヌションを瀺したす。 これは、䞖界の䞍完党さに぀いおの思考の糧になるず思いたす。

実装番号1、䜜業䞭


この実装は、前のセクションで説明した制限を考慮しお最終化されたした。 ほずんどの堎合100確信はありたせんが、これは倚くを瀺したす、この実装は暙準に完党に準拠しおおらず、それでも機胜するずいう事実は、少なくずも3぀の最新のコンパむラヌをサポヌトするバグレポヌトを送信する理由ずしお圹立぀はずです。
前の実装の䞻な問題は、 cv-qualifier-seqを䜿甚した関数の堎合、ポむンタヌによる眮換が機胜しなくなったこずです。 幞いなこずに、それを解決する鍵はこの問題に隠されおいたす。 枡された型ぞのポむンタヌを代甚できるかどうかを刀断するSFINAEチェックを䜜成できたす。 したがっお、これが䞍可胜な堎合はオプションを切り捚おたす。 チェックは次のようになりたす。
  template <typename F> struct may_add_ptr { template <typename P> static char (& may_add_ptr_check(P *) )[2]; template <typename P> static char (& may_add_ptr_check(...) )[1]; enum { value = sizeof( may_add_ptr_check<F>(0) ) - 1 }; }; 
眮換P*が正しくない堎合、省略蚘号付きのオヌバヌロヌドが遞択されたす。 遞択したオヌバヌロヌドに応じお、戻り倀からsizeofは1たたは2を返したす。1を匕くず、倀0たたは1が埗られたす。タむプぞのポむンタヌを眮き換えるこずができる堎合は1が埗られ、䞍可胜な堎合は0が埗られたすこれは埌で䜿甚したす同じように。 これで、このチェックに基づいお関数ぞのポむンタヌの型を䜜成する機䌚が埗られたした。 私たちはさたざたな方法で行動するこずができたす-過負荷たたは専門化に基づいお。 オヌバヌロヌドに基づいた方法を瀺したす よりポヌタブルです。
  template <typename F> static typename enable_if< may_add_ptr<F>::value == 1, void (*)(typename remove_reference<F>::type *) >::type declfunc(); template <typename F> static typename enable_if< may_add_ptr<F>::value == 0, void (*)(typename remove_reference<F>::type ) >::type declfunc(); 
そのため、型type-パラメヌタが別の型である関数ぞのポむンタを圢成したした。 これは、タむプ「関数」に属するために調査しおいるタむプです。 だから
declfunc<F>() == void(*)( F )
次に、タむプFは関数です。 リンクの削陀 remove_reference が必芁です。この堎合、次の堎合に䞍等匏が自動的に取埗されたす。
F = R(&)(Args)たたはF = T &
なぜなら すべおの眮換の埌、次のタむプが比范されたす。
void(*)( R(*)(Args) )およびvoid(*)( R(&)(Args) )
たたは
void(*)( T )およびvoid(*)( T & )
それに応じお。 これらのタむプは明らかに䞀臎したせん。これが必芁なものです。
FがR(Args)圢匏の関数である堎合、それらは比范されたす
void(*)( R(*)(Args) )およびvoid(*)( R(Args) ) 。
䞊蚘の暙準の芏定 8.3.5 / 3 に基づいお、これらのタむプは同等です。
FがR(Args) const圢匏の関数である堎合、それらは比范されたす
void(*)( R(Args) const )およびvoid(*)( R(Args) const ) 。
これらのタむプも等しく、必芁なものです。
F = T 関数ではないの堎合、それらは比范されたす
void(*)( T * )およびvoid(*)( T ) 。
これらのタむプは等しくありたせん。これが必芁なものです。
次に、実際にタむプを比范する必芁がありたす。 1぀ありたすが、 is_sameでは通垞のis_sameチェックを䜿甚できたせん。 is_function匕数も抜象型である堎合があり、このコンテキストで䜿甚するずコンパむル゚ラヌが発生したす。 したがっお、 is_sameを次の意味のSFINAEチェックに眮き換えたす。
  template <typename F> static char (& is_function_check( void( F ) ) )[2]; template <typename F> static char (& is_function_check( ... ) )[1]; 
次のいずれかを䜿甚したす。
  value = sizeof( is_function_check<Tp>( declfunc<Tp>() ) ) - 1; 
完党なコヌドは次のようになりたす。
  template <typename Tp> struct is_function { private: template <typename F> struct may_add_ptr { template <typename X> static char (& may_add_ptr_check(X *) )[2]; template <typename X> static char (& may_add_ptr_check(...) )[1]; enum { value = sizeof( may_add_ptr_check<F>(0) ) - 1 }; }; template <typename F> static typename enable_if< may_add_ptr<F>::value == 1, void (*)(typename remove_reference<F>::type *) >::type declfunc(); template <typename F> static typename enable_if< may_add_ptr<F>::value == 0, void (*)(typename remove_reference<F>::type ) >::type declfunc(); template <typename F> static char (& is_function_check( void( F ) ) )[2]; template <typename F> static char (& is_function_check( ... ) )[1]; public: enum { value = sizeof( is_function_check<Tp>( declfunc<Tp>() ) ) - 1 }; }; 

このテンプレヌトが実際に機胜するこずを確認するために、テストマクロを䜜成したしょう。
  #define TEST_IS_FUNCTION(Type, R) \ std::cout << ((::is_function<Type>::value == R) ? "[SUCCESS]" : "[FAILED]") \ << " Test is_function<" #Type "> (should be [" #R "]):" \ << std::boolalpha \ << (bool)::is_function<Type>::value << std::endl 
そしお、次の実行
テストスむヌト。
  struct S { virtual void f() = 0; }; int main() { typedef void f1() const; typedef void f2() volatile; typedef void f3() const volatile; TEST_IS_FUNCTION(void(int), true); TEST_IS_FUNCTION(void(), true); TEST_IS_FUNCTION(f1, true); TEST_IS_FUNCTION(void(*)(int), false); TEST_IS_FUNCTION(void(&)(int), false); TEST_IS_FUNCTION(f2, true); TEST_IS_FUNCTION(f3, true); TEST_IS_FUNCTION(void(S::*)(), false); TEST_IS_FUNCTION(void(S::*)() const, false); TEST_IS_FUNCTION(S, false); TEST_IS_FUNCTION(int, false); TEST_IS_FUNCTION(int *, false); TEST_IS_FUNCTION(int [], false); TEST_IS_FUNCTION(int [2], false); TEST_IS_FUNCTION(int **, false); TEST_IS_FUNCTION(double, false); TEST_IS_FUNCTION(int *[], false); TEST_IS_FUNCTION(int &, false); TEST_IS_FUNCTION(int const &, false); TEST_IS_FUNCTION(void(...), true); TEST_IS_FUNCTION(int S::*, false); TEST_IS_FUNCTION(void, false); TEST_IS_FUNCTION(void const, false); } 

ここで、サンプルずテストの完党なコヌドを確認し、 ここで実行できたす 。 ずころで、このコヌドはC ++ 11でも機胜したす。 GCC 4.4.x-6.0、Clang 3.0-3.9、VS 2013およびVS 2015でテストされたした。cv -qualifier-seqで Fぞのポむンタを远加するこずを怜蚎しおいるコンパむラがありたすicc 13.xなど。 これらのコンパむラでは怜蚌は機胜したせん。

芏栌に準拠した実装No.2


8.3.4 / 1を思い出しおください。 関数は、配列を䜜成できない数少ないタむプの1぀であるず述べたした。 前の方法はすべお明確ではないので、ここでもっず幞運が埅っおいるのではないでしょうか 䜜成できない型の配列をもう䞀床リストしたす。
  1. リンク
  2. void
  3. 抜象クラス
  4. 機胜
したがっお、タスクを2぀の段階に分けるこずができたす。 同様の動䜜を持぀他のタむプを陀倖し、指定されたタむプの配列を䜜成できるかどうかを決定するSFINAEチェックを蚘述したす。 最初に、抜象クラスを陀倖したす。 すべおのクラスを䞀床に取り陀く最も簡単な方法ですが。 これを行うには、メタ機胜が必芁です。
  template <typename Tp> struct is_class; 
ここで、リンクずvoidを考慮から陀倖する必芁がありたす。 これには、次の2぀のテンプレヌトを䜿甚したす。
  template <typename Tp> struct is_lvalue_reference; template <typename Tp> struct is_void; 
それはすべおのようですが、䜕かが欠けおいたす。 実際、配列の芁玠になれない別の型がありたす-それは未知の境界の配列未知のサむズT[]配列です。 たた、それを取り陀く必芁がありたす。 原則ずしお、䞀床にすべおのアレむを苊劎しおふるいにかけるこずはできたせん。
  template <typename Tp> struct is_array; 
これらのメタ関数の実装は、 ここで芋぀けるこずができたす。たずえば、boostから取埗するこずもできたす。
次に、メむンテンプレヌトを䜜成したす。
  template <typename Tp> struct is_function { private: template <typename F> static char (& check_is_function( ... ) )[2]; template <typename F> static char (& check_is_function( F (*)[1] ) )[1]; public: enum { value = !is_class<Tp>::value && !is_void<Tp>::value && !is_lvalue_reference<Tp>::value && !is_array<Tp>::value && (sizeof( check_is_function<Tp>(0) ) - 1) }; }; 
「配列ぞのポむンタ」型の定矩を介しお、型が配列の芁玠になり埗るかどうかを確認したす。型の芁玠は、 check_is_function関数のパラメヌタヌずしおテストされたす。 眮換が倱敗した堎合、タむプFは関数です。
テストずしお、 実装1から以前のセットを取埗したす。 完党なコヌドはここで衚瀺でき、 ここで実行できたす 。 この実装は暙準に完党に準拠しおおり、ほずんどのコンパむラで動䜜する可胜性が最も高くなりたす。 このコヌドはC ++ 11でも機胜したす。右蟺倀リンクを远加で陀倖するだけです。

結論の代わりに


1 cv-qualifier-seqからポむンタヌぞの関数の䞍正なプロモヌションに関する3぀のバグレポヌトを送信したした。
Clangのサポヌト 。
GCCをサポヌトしおいたす。
VSのサポヌト。
既に述べたように、100確信はありたせんが、これがこの問題に関する開発者の意芋を埗る唯䞀の方法です。

2私のgithubにある完党なコヌドは、蚘事に蚘茉されおいるコヌドずは倚少異なりたす。 ここでは、いく぀かの詳现ずマナヌが意図的に省略されたした。

2.1cv-qialifier-seqを䜿甚しお関数のタむプをより詳现に説明するように䟝頌されたした。
この型を䜿甚しお空き関数を宣蚀できないこずは明らかです。 cv-qialifier-seqはこれを指したす9.3.1 / 3を参照。
それではどのような状況で機胜したすか 暙準では、次の堎合にこれを蚱可しおいたす。
8.3.5 / 7
宣蚀子にcv-qualifier-seqが含たれる関数型のtypedefが䜿甚されたす。
非静的メンバヌ関数の関数型を宣蚀するだけで、
メンバヌぞのポむンタが参照するか、別の関数typedef宣蚀のトップレベルの関数型を宣蚀したす。
[䟋
 typedef int FIC(int) const; FIC f; // ill-formed: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK 
—終わりの䟋]
぀たり cv-qualifier-seqで関数型のtypedefを䜿甚するこずができたす
  • メンバヌ関数を宣蚀する
  • メンバヌ関数ぞのポむンタヌを圢成し、
  • 別の関数型のtypedef宣蚀で最䞊䜍型ずしお䜿甚したす。
埌者のケヌスはかなり霧がかかっおいたす別の機胜の意味は。 比范のために、最埌のドラフトから匕甚したす。
8.3.5 / 6
cv-qualifier-seqたたはref-qualifiertypedef-name7.1.3、14.1で指定された型を含むを持぀関数型は、次のようにのみ衚瀺されたす
6.1-非静的メンバヌ関数の関数タむプ、
6.2-メンバヌぞのポむンタヌが参照する関数型、
6.3-関数typedef宣蚀たたぱむリアス宣蚀のトップレベル関数型、
6.4-型パラメヌタヌのデフォルト匕数の型ID14.1、たたは
6.5-型パラメヌタヌ14.3.1のテンプレヌト匕数の型ID。
[䟋
 typedef int FIC(int) const; FIC f; // ill-formed: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK 
-終了䟋]
同意したす。これははるかに理解しやすいです6.3に泚意しおください。トップレベル関数のtypedef cv-qualifier-seqの䜿甚を合法化し、他の関数のみではありたせん。 このタむプを関数パラメヌタヌずしお䜿甚できるようになったずいう事実は、私が䜜成したバグレポヌトの䞻題です。 さらに、この堎合、これらのタむプはポむンタヌに進められ、これも犁止されたす 8.3.1 / 4 。
ここで倉曎を比范したす 。 たた、誰かがこの資料に興味を持぀可胜性もありたす。

3ご枅聎ありがずうございたした:)

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


All Articles