最近GCCを8.2にアップグレードしましたが、ほとんどのSFINAE式が機能しなくなりました。
以下はやや簡略化されていますが、問題を示しています。
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename std::enable_if<
std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename std::enable_if<
!std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
古いバージョンのGCC(残念ながら、以前にインストールした正確なバージョンを覚えていません)とClangは上記のコードを正常にコンパイルしますが、GCC8.2では次のようなエラーが発生します。
:関数 'int main()': :29:19:エラー:オーバーロードされた 'test()'の呼び出しがあいまいです c.test(); ^ :12:10:注:候補: 'void Class :: test()[with U = int&typename std :: enable_if :: type> :: value> :: type ... = {}]' void test(){ ^ ~~~ :22:10:注:候補: 'void Class :: test()[with U = int&typename std :: enable_if :: type> :: value)> :: type ... = {}] ' void test(){ ^ ~~~ :30:25:エラー:オーバーロードされた 'test()'の呼び出しがあいまいです c.test(); ^ :12:10:注:候補: 'void Class :: test( )[with U = const int&typename std :: enable_if :: type> :: value> :: type ... = {}] ' void test(){ ^ ~~~ :22:10:注:候補: 'void Class :: test()[with U = const int&typename std :: enable_if :: type> :: value)> :: type ... = { }] ' void test(){
異なるコンパイラーとコンパイラーバージョンが同じコードを異なる方法で処理する場合に通常そうであるように、私は未定義の動作を呼び出していると思います。規格は上記のコードについて何と言っていますか?私は何が間違っているのですか?
注:問題はこれを修正する方法ではなく、いくつか頭に浮かぶことがあります。問題は、なぜこれがGCC 8で機能しないのか、それとも標準で定義されていないのか、それともコンパイラのバグなのかということです。
注2:全員がデフォルトのvoid
タイプのstd::enable_if
にジャンプしていたので、使用する質問を変更しました代わりにint
。問題は残っています。
これは私の見解です。つまり、clangは正しく、gccには回帰があります。
[temp.deduct] p7 によると:
置換は、関数型およびテンプレートパラメータ宣言で使用されるすべての型と式で発生します。 [...]
これは、パックが空であるかどうかに関係なく、置換が行われる必要があることを意味します。私たちはまだ当面の状況にあるため、これはSFINAE対応です。
次に、可変個引数パラメーターが実際のテンプレートパラメーターと見なされることがわかります。から [temp.variadic] p1
テンプレートパラメータパックは、0個以上のテンプレート引数を受け入れるテンプレートパラメータです。
および [temp.param] p2 は、どの非型テンプレートパラメータが許可されるかを示します。
非型テンプレートパラメータは、次の(オプションでcv修飾された)型のいずれかを持つ必要があります。
リテラルであり、構造的に同等であり([class.compare.default])、可変または揮発性のサブオブジェクトがなく、デフォルトのメンバー演算子<=>がある場合は、パブリックとして宣言されます。
左辺値参照型、
プレースホルダータイプ([dcl.spec.auto])を含むタイプ、または
推定クラスタイプのプレースホルダー([dcl.type.class.deduct])。
void
は法案に適合しないことに注意してください。あなたのコード(投稿されたとおり)は形式が正しくありません。
私は言語弁護士ではありませんが、次の引用はどういうわけか問題に関連していることができませんか?
[temp.deduct.type/9] :Piがパック拡張の場合、Piのパターンは、Aのテンプレート引数リストに残っている各引数と比較されます。各比較により、後続のテンプレート引数が推定されます。 Piによって拡張されたテンプレートパラメータパック内の位置。
テンプレート引数リストに残っている引数がないため、pattern(enable_if
を含む)の比較はないように思われます。比較がなければ、控除もありませんし、控除後に代用が発生すると思います。したがって、置換がない場合、SFINAEは適用されません。
私が間違っている場合は私を訂正してください。この特定の段落がここに当てはまるかどうかはわかりませんが、[temp.deduct]にはパックの拡張に関してより類似したルールがあります。また、このディスカッションは、経験豊富な人が問題全体を解決するのに役立ちます: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A 。
部分的な回答:異なるT
sでtypename = typename enable_if<...>, T=0
を使用します。
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename = typename std::enable_if_t<
std::is_const<typename std::remove_reference<U>::type>::value
>, int = 0
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename = typename std::enable_if_t<
!std::is_const<typename std::remove_reference<U>::type>::value
>, char = 0
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
( デモ )
std::enable_if<...>::type...
は、 デフォルトのタイプはvoid
を知っていることを意味します。