質問
明示的な末尾の戻り値の種類と自動末尾の戻り値の間に奇妙な違いを見つけました。
次のコードでは、このタイプの1つのオブジェクトを引数として1つのオブジェクトにする整数およびITER関数でテンプレートを定義します。戻り値は、テンプレート値をデクリメントした後に自分自身を呼び出す結果によって異なります。
インスタンス化ループを破る(または私が考えた)、私は非依存型を返す特殊化を提供します。
テンプレートをインスタンス化するためのおもちゃのメインがあります。
これはちょっとしたコードです:
template<int i> struct Int {};
constexpr auto iter(Int<0>) -> Int<0>;
template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));
int main(){
decltype(iter(Int<10>{})) a;
}
.
GCC 4.9とClang 3.5の両方では機能しません。どちらも無限のインスタンス化をトリガーします(それらは特殊なベースケースと一致しません)。
rec.cpp:11:62: fatal error: recursive template instantiation exceeded maximum depth of 256
template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));
.
今、C ++ 14 decltype(auto)
を使用していれば、まったく同じことを返すテンプレートのためのボディを提供します。
template<int i> struct Int {};
constexpr auto iter(Int<0>) -> Int<0>;
template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) {
return iter(Int<i-1>{});
}
int main(){
decltype(iter(Int<10>{})) a;
}
.
これは現在、両方のコンパイラで機能し、期待通りに動作します。
私は専門化を表現するためのさまざまな方法を試してみましたが、それを少し周りに移動しましたが、それはその自己固定を妨げませんでした。(
私はまた、より多くのdecltype
とdeclval
を使用してコードを振りかけようとしましたが、C ++ 11の構文が作業しているようです。
誰かが名前検索の2つの構文の違いを説明することができますか?
解決
過負荷解像度、テンプレート過負荷解像度、テンプレート宣言のインスタンス化、テンプレート定義インスタンス化の相対順序付けのためです。
最初にC ++ 11のケースを見てみましょう。コンパイラがdecltype(iter(Int<0>{}))
を評価する必要がある場合は、引数PRValue iter
を使用して呼び出された名前のInt<0>
のオーバーロード解決を実行します。テンプレートが過負荷セットにあるため、14.8.3 [Temp.Over] :
1 - 関数テンプレートは、その名前の(非テンプレート)関数または同じ名前の(その他)関数テンプレートのどちらかでオーバーロードできます。その名前への呼び出しが(明示的に、または暗黙的にオペレータ表記を使用している)の場合、テンプレート引数除算(14.8.2)と明示的なテンプレート引数(14.3)の検査は、テンプレート引数値を見つけるために各関数テンプレートに対して実行されます(14.3)。もしあれば) その関数テンプレートは、呼び出し引数で呼び出すことができる関数テンプレートの特殊化をインスタンス化するためのテンプレート。 [...]
その結果、宣言template<int i> constexpr auto iter(...) -> ...
は、i = 0
を使用してインスタンス化されます(14.7.1p10 [temp.inst] )、decltype(iter(Int<-1>{}))
の評価を強制し、それは私たちが行く負の整数のウサギの穴の評価を強います。
constexpr auto iter(Int<0>) -> Int<0>
がより良い過負荷であることは関係ありません(13.3.3p1 [over.match.best] )、これまでのとは決して得られません。コンパイラは、マイナスの無限大に向かってマーキーを行進しています。
対照的に、C ++ 14の推定戻り値7.1.6.4p12 [dcl.spec.auto] 適用:
12 - 宣言型のファンクションテンプレートの戻り型除算は、定義がインスタンス化されたときに発生します[...]
定義のインスタンス化はテンプレートの過負荷解像度(14.7.1p3)の後、Bad Template iter<0>
はインスタンス化されていません。 14.8.3p5:
5 - 関数テンプレートの専門化の署名のみが、一連の候補関数で専門化を入力するために必要です。したがって、テンプレートの専門化が候補者があるコールを解決するためには、関数テンプレート宣言のみが必要です。
iter<0>
の「シグネチャ」は(Int<0>) -> decltype(auto)
、プレースホルダタイプ(7.1.6.4)を含む署名です。
推奨回避策:iter(Int<-1>{})
への呼び出しが試行されたことを防ぐためにSFINAEを使用します。
template<int i> constexpr auto iter(Int<i>)
-> decltype(iter(typename std::enable_if<i != 0, Int<i-1>>::type{}));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^
.
SFINAEは、
の内部の内部にGO を維持し、実際にはdecltype
への呼び出し内部です。