https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86689
Bug ID: 86689 Summary: Some combination of SFINAE, overloading, and type deduction showing version inconsistency Product: gcc Version: 8.2.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: bugs at grumpyplatypus dot com Target Milestone: --- The following code is accepted by GCC 4.7.3-7.3.0, but 8.1.0 and 8.2.0 do not accept it: // Fail.cpp template <typename T> struct SomeClassTemplate {}; struct TrueType { static constexpr bool value = true; }; struct FalseType { static constexpr bool value = false; }; template <bool B, typename T=void> struct EnableIfT {}; template <typename T> struct EnableIfT<true,T> { using type = T; }; template <typename T> struct IsAType : TrueType {}; template <typename Pred,typename T=void> using EnableIf = typename EnableIfT<Pred::value,T>::type; template <typename T, typename=EnableIf<IsAType<T>,int>> int SomeFunc(T const&) { return 1; } template <typename T, typename=EnableIf<IsAType<T>,int>> int SomeFunc(SomeClassTemplate<T> const&) { return -1; } template int SomeFunc(double const&); template int SomeFunc(SomeClassTemplate<double> const&); int main() { double x; SomeClassTemplate<double> y; auto a = SomeFunc(x); auto b = SomeFunc(y); return a + b; } // End Fail.cpp Sorry about all manual EnableIf and whatnot -- I wanted #include-less code. Also, the predicate is dumb, but meant to match the case in "real code". The compiler error is: > g++ Fail.cpp Fail.cpp:22:14: error: ambiguous template specialization ‘SomeFunc<>’ for ‘int SomeFunc(const SomeClassTemplate<double>&)’ template int SomeFunc(SomeClassTemplate<double> const&); ^~~~~~~~ Fail.cpp:16:5: note: candidates are: ‘template<class T, class> int SomeFunc(const T&)’ int SomeFunc(T const&) { return 1; } ^~~~~~~~ Fail.cpp:19:5: note: ‘template<class T, class> int SomeFunc(const SomeClassTemplate<T>&)’ int SomeFunc(SomeClassTemplate<T> const&) { return -1; } I am especially confused since commenting out the explicit instantiation of this function allows compilation to succeed, in particular the implicit instantiation in main() is successful and chooses the correct overload. Some other details: > g++ -v Using built-in specs. COLLECT_GCC=g++ COLLECT_LTO_WRAPPER=/path/to/gcc/8.1.0/libexec/gcc/x86_64-pc-linux-gnu/8.1.0/lto-wrapper Target: x86_64-pc-linux-gnu Configured with: ./configure --prefix=/path/to/gcc/8.1.0 --enable-languages=c,c++,fortran,objc,obj-c++,lto --disable-multilib Thread model: posix gcc version 8.1.0 (GCC) > g++ -v Using built-in specs. COLLECT_GCC=g++ COLLECT_LTO_WRAPPER=/path/to/gcc/8.2.0/libexec/gcc/x86_64-pc-linux-gnu/8.2.0/lto-wrapper Target: x86_64-pc-linux-gnu Configured with: ./configure --prefix=/path/to/gcc/8.2.0 --enable-languages=c,c++,fortran,objc,obj-c++,lto --disable-multilib Thread model: posix gcc version 8.2.0 (GCC) This code is accepted, with at least -std=c++11, by GCC 4.7.3-7.3.0, the latest clang, both release and git versions, as well as Intel 18.0.2 and 19.0-beta, PGI 18.5, and XL. Also, the following code compiles without issue with 8.1.0 and 8.2.0: // Ok.cpp template <typename T> struct SomeClassTemplate {}; struct TrueType { static constexpr bool value = true; }; struct FalseType { static constexpr bool value = false; }; template <bool B, typename T=void> struct EnableIfT {}; template <typename T> struct EnableIfT<true,T> { using type = T; }; template <typename T> struct IsAType : TrueType {}; template <typename Pred,typename T=void> using EnableIf = typename EnableIfT<Pred::value,T>::type; template <typename T, EnableIf<IsAType<T>>* = nullptr> int SomeFunc(T const&) { return 1; } template <typename T, EnableIf<IsAType<T>>* = nullptr> int SomeFunc(SomeClassTemplate<T> const&) { return -1; } template int SomeFunc(double const&); template int SomeFunc(SomeClassTemplate<double> const&); int main() { double x; SomeClassTemplate<double> y; auto a = SomeFunc(x); auto b = SomeFunc(y); return a + b; } // End ok.cpp The only difference between this and Fail.cpp is in EnableIf style. > g++ Ok.cpp > I'm concerned that it accepts Ok.cpp but not Fail.cpp (or that it does not accept Fail.cpp and does accept Ok.cpp), but I don't know the standard well enough to say 100% that one or the other or both is right or wrong. Anyway, this issue breaks a bunch of code that I didn't write but that I have to use, so any clarification would be most appreciated.