私は「怠惰な男のenable_if
」と呼ぶ手法を頻繁に使用します。ここでは、decltype
とコンマ演算子を使用して、テンプレート入力に基づいて関数を有効にします。ここに小さな例があります:
template <typename F>
auto foo(F&& f) -> decltype(f(0), void())
{
std::cout << "1" << std::endl;
}
template <typename F>
auto foo(F&& f) -> decltype(f(0, 1), void())
{
std::cout << "2" << std::endl;
}
--std=c++11
を使用すると、g ++ 4.7 +およびClang3.5 +は、そのコードを問題なくコンパイルします(そして、私が期待するとおりに機能します)。ただし、MSVC 14 CTP5を使用すると、foo
がすでに定義されているというエラーが表示されます。
エラーエラーC2995: 'unknown-type foo(F &&)':関数テンプレートはすでに定義されていますc ++-スクラッチmain.cpp15
だから私の質問は:「怠惰な男のenable_if
」は合法的なC++ですか、それともこれはMSVCのバグですか?
[temp.over.link]/6 2つの関数テンプレート宣言がオーバーロードになるタイミングを指定します。これは、2つの関数テンプレートの同等性を次のように定義することによって行われます。
2つの関数テンプレートは、テンプレートパラメータを含む式を比較するために上記のルールを使用して同等の戻り値の型[..]を持っている場合、同等です。
「上記のルール」は
式を含む2つの関数定義が1つの定義規則(3.2)[..]を満たす場合、テンプレートパラメータを含む2つの式は同等と見なされます。
この部分に関連するODRは、 [basic.def.odr]/6 と述べています。
D
という名前のエンティティが複数の翻訳単位で定義されている場合、
D
の各定義は、同じトークンのシーケンスで構成されます;
明らかに、戻り値の型( [dcl.fct]/2 による末尾の戻り値の型)は同じトークンで構成されていないため、これらの式を含む2つの関数定義はODRに違反します。
したがって、foo
の宣言は、同等でない関数テンプレートを宣言し、名前をオーバーロードします。
表示されるエラーは、式SFINAEに対するVC++からのサポートがないために発行されます。おそらく、末尾の戻り値の型が同等であるかどうかは検査されません。
別の方法で関数テンプレートを非同等にすることができます-テンプレートパラメータリストを変更します。 2番目の定義を次のように書き直すと、次のようになります。
template <typename F, int=0>
auto foo(F&& f) -> decltype(f(0, 1), void())
{
std::cout << "2" << std::endl;
}
次に、VC++ 正常にコンパイルされます 。 [temp.over.link]/6の引用を短くしました。これは、次のことをカバーしています。
2つの関数テンプレートは、同じスコープで宣言され、同じ名前を持ち、同じテンプレートパラメーターリストを持っている [..]の場合、同等です。
実際、新しいオーバーロードを簡単に導入できるようにするために、小さなヘルパーを使用できます。
template <int I>
using overload = std::integral_constant<int, I>*;
使用法は、例えばです。
// Remember to separate > and = with whitespace
template <typename... F, overload<0> = nullptr>
auto foo(F&&... f) -> decltype(f(0, 1)..., void())
template <typename... F, overload<1> = nullptr>
auto foo(F&&... f) -> decltype(f(0, 1, 2)..., void())
デモ 。
これは "Expression SFINAE。" と呼ばれる機能です。VisualC++ではまだ完全にはサポートされていません( "VS2015プレビューのC++ 11/14/17機能" を参照)。この回答の時点での最新の適合性更新について)。