5

次のコードは、IタイプT特定のクラステンプレートのインスタンスであるかどうかを決定するために使用されているC++テンプレートメタプログラミングパターンのコアを実証しますファンクションテンプレートisSの2つのオーバーロードがあり、期待通りに1を出力します。私は、すなわち二isS、からポインタを削除すると、プログラムが予期せず0出力関数テンプレートポインタ引数でオーバーロード解決

template<class T> 
constexpr bool isS(const T) {return false;} 

と交換してください。 isSの両方のバージョンでコンパイルのオーバーロード解決フェーズに進むと、出力はコンパイラーが2番目のオーバーロードを選択していることを意味します。私はこれをGCC、Clang、およびvC++の下でオンラインコンパイラhereを使ってテストしました。これらはすべて同じ結果を生成します。なぜこれが起こるのですか?

私はHerb Sutterの記事"Why Not Specialize Function Templates"を何度も読んでおり、isSの機能は両方とも基本テンプレートと見なされているようです。これがそうであれば、それはどちらが最も専門的であるかの問題です。直観とthis answerによると、最初のisSTS<A,B>*のすべてのインスタンスと一致する可能性があり、S<A,B>*と一致できないTの可能性のあるインスタンス化が多数あるため、最も専門的なものであることが期待されます。私は、この動作を定義している作業草案で段落を見つけたいと思いますが、コンパイルのどのフェーズが問題を引き起こしているかは完全にはわかりません。 "14.8.2.4部分的な順序付け中にテンプレート引数を控除する"と関係があるか

この問題は最初isSconst S<A,B>への参照を取得し、第二のconst Tをとる次のコードは、期待値1出力することを考えると、特に驚くべきことである:

#include <iostream> 

template<class A, class B> 
struct S{}; 

template<class A, class B> 
constexpr bool isS(const S<A,B>&) {return true;} 

template<class T> 
constexpr bool isS(const T) {return false;} 

int main() { 
    S<int,char> s; 
    std::cout<<isS(s)<<std::endl; 
    return 0; 
} 

をそれで問題がように見えますポインタがどのように扱われるかと関係があります。

+0

'const T'パラメータは' T'パラメータと同じです(正確な一致を得るため)。 'S *'から 'S const *'への修飾変換が必要です。代わりに、 'main'に' S const s; 'を使ってみてください。 - 他のテストで過負荷を判断できなかった場合、*より特殊な*テストが過負荷解決の非常に遅れて発生します。ここでは、より良いランク(正確な一致と資格の調整)があるため、2番目のものを先に選択することができます。 – dyp

+0

@dypしかし、コンパイラはなぜ、参照を含むコードスニペットの一番下の 'isS'を選ぶのですか?この場合、コンパイラは 'S &'から 'const S &'への修飾変換を行う必要はありませんか? – Ose

+0

@Oseいいえ、 'T *'と 'const T *'は修飾修飾の変換です。 'T'は' const T& 'への参照変換であり、引数に直接バインドします。 – TartanLlama

答えて

6

constは、2番目のオーバーロードがconst Tの内部に落ちるため、引き算中にT*に解決されます。最初のオーバーロードはS<int, char> const*に解決されるため、より厳密な一致になります。これはconst修飾の変換を必要とします。

あなたがでキックするために、より専門的な過負荷のためのために、あなたの変数sの前でconstを追加する必要があります。

#include <iostream> 

template<class A, class B> 
struct S {}; 

template<class A, class B> 
constexpr bool isS(const S<A,B>*) {return true;} 

//template<class T> 
//constexpr bool isS(const T*) {return false;} 

template<class T> 
constexpr bool isS(const T) {return false;} 

int main() { 
    S<int,char> const s{}; // add const here 
    std::cout<<isS(&s)<<std::endl; 
    return 0; 
} 

Live Example

const S<A,B>&に最初のオーバーロードを変更、正しいを与えます結果として、資格調整の代わりにID変換が行われるためです。

13.3.3.1.4リファレンス結合[over.ics.ref]

1参照型のパラメータは、直接(8.5結合する場合。3)を引数式に使用する場合、 引数式が パラメータタイプの派生クラスである型を持たない限り、 暗黙の変換シーケンスはID変換です。この場合、暗黙の変換シーケンスは の導出 - ベース変換(13.3.3.1)。

:な引数控除のゲームについての疑いで、それはあなたの選択したテンプレートの推定されるタイプの詳細な情報を与えるであろう(GCC /打ち鳴らす上)__PRETTY_FUNCTION__マクロを使用するために便利です。次に、特定の過負荷をコメントアウトして、過負荷の解決にどのように影響するかを確認することができます。このlive exampleを参照してください。

+0

ありがとうございますが、上記の私のコメント(質問の下)をご覧ください。 – Ose

+0

@Ose tnx、updated。 – TemplateRex

+0

偉大な、標準への参照のおかげで! – Ose

3

isSの最初のバージョンでは暗黙的な変換が必要なため、2番目のバージョンでは必要ないため、2番目のバージョンで期待される答えが得られません。

template<class A, class B> 
constexpr bool isS(const S<A,B>*); 

template<class T> 
constexpr bool isS(const T); 

S<int,char> s; 
isS(&s); 

&sはタイプS<int,char>*であることに注意してください。最初のisSconst S<int,char>*を探しているので、ポインタに変換が必要です。 2番目のisSは直接の一致です。


あなた自身が、多くの場合、このパターンを必要とする見つけた場合、あなたはそのように、それを一般化できますタイプは、このようなSの専門である場合

template<template<typename...> class TT, typename T> 
struct is_specialization_of : std::false_type {}; 

template<template<typename...> class TT, typename... Ts> 
struct is_specialization_of<TT, TT<Ts...>> : std::true_type {}; 

を次に、あなたがチェック:

is_specialization_of<S, decltype(s)>::value 
+0

ありがとうございますが、上記の私のコメント(質問の下)をご覧ください。また、一般化のための+1 - 私はそれを行うことを考えていなかった! – Ose

+0

私はあなたの一般化の2つの欠点に気付きました。 1つはconst TT はTT の特殊化ではないということです。これが望ましい場合(私の場合)、const TT の2番目の特殊化を追加する必要があります。もう1つの欠点は、タイプと非タイプのパラメータが混在しているクラステンプレートでは使用できないということです(実際にはほとんどの場合です)。このケースをカバーするためにそれをさらに一般化する方法は見当たりません。 – Ose

+0

最初の欠点は、型を最初に 'std :: remove_const'を呼び出すテンプレートエイリアスでラップするか、' std: :すべてを取り除くために崩壊する。 2番目の問題は私が以前に遭遇した問題であり、まだまだ良い解決策はありません。 – TartanLlama

関連する問題