Why do my SFINAE expressions no longer work with GCC 8.2?
This is my take on it. In short, clang is right and gcc has a regression.
We have according to [temp.deduct]p7:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. [...]
This means that the substitution has to happen whether or not the pack is empty or not. Because we are still in the immediate context, this is SFINAE-able.
Next we have that a variadic parameter is indeed considered an actual template parameter; from [temp.variadic]p1
A template parameter pack is a template parameter that accepts zero or more template arguments.
and [temp.param]p2 says which non-type template parameters are allowed:
A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
a type that is literal, has strong structural equality ([class.compare.default]), has no mutable or volatile subobjects, and in which if there is a defaulted member operator<=>, then it is declared public,
an lvalue reference type,
a type that contains a placeholder type ([dcl.spec.auto]), or
a placeholder for a deduced class type ([dcl.type.class.deduct]).
Note that void
doesn't fit the bill, your code (as posted) is ill-formed.
SFINAE expression fails to compile with clang
I think this is a gcc bug actually, as a result of [temp.class.spec]:
The type of a template parameter corresponding to a specialized
non-type argument shall not be dependent on a parameter of the
specialization. [ Example:template <class T, T t> struct C {};
template <class T> struct C<T, 1>; // error
template< int X, int (*array_ptr)[X] > class A {};
int array[5];
template< int X > class A<X,&array> { }; // error
—end example ]
In your example, the type of the 3rd template parameter is dependent on a parameter. When you swap it to typename = std::enable_if_t<...>
, then this rule no longer applies.
Note: is there any reason to use SFINAE here anyway, as opposed to static_assert
-ing?
Why this SFINAE snippet is not working in g++, but working in MSVC?
This has absolutely nothing to do with whether default template arguments are part of a function template's signature.
The real problem is that T
is a class template parameter, and when you instantiate the class template's definition, the implementation can immediately substitute it into your default template argument, decltype(std::declval<T>().size())
outside of template argument deduction, which causes a hard error if size
is not present.
The fix is simple; simply make it depend on a parameter of the function template.
template <typename U, typename = decltype(std::declval<U>().size())>
static std::true_type check(U);
(There are other problems with your implementation, such as it requires a move-constructible non-abstract T
and doesn't require size()
to be const-callable, but they aren't the cause of the error you are seeing.)
C++ template sfinae error
SFINAE (the S is "substitution", not "specialization", by the way) for member functions does not work this way with class type parameters. A simple way to get around that is to use another template parameter:
template<
typename TCopy = T
typename boost::enable_if< boost::is_same<myStruct, TCopy>, int >::type = 0
> void print()
{
std::cout << "this is my struct" << std::endl;
}
I pulled this from STL's CppCon talk. TL;DW: when using the class's T
type parameter, the compiler knows what that type is when instantiating the class template and would check for the type
member at that point in time. With the extra TCopy
type parameter being local to the function, the compiler cannot know for sure until the function is instantiated, at which point SFINAE can jump in to affect the overload set for the call.
Using SFINAE to detect method with GCC
In the event someone else encounters a similar error/misunderstanding, my error (aptly pointed out by n. 'pronouns' m.) was using the wrong type in the has_serialize::test
. From what I can infer (in my naivety) is that for
template<typename T>
struct has_serialize
{
// ...
template<typename C>
static constexpr auto test(int)
-> decltype(std::declval<T>().serialize(std::declval<SerializerBase&>()), std::true_type());
// ...
};
the template C
does not appear in has_serialize::test
, and as a result GCC does not provide an opportunity for substitution failure. Consequently, GCC tries to evaluate std::declval<T>().serialize(...)
and obviously throws an error when T = std::vector<double>
. If T
is replaced with C
in this line, then the compiler recognizes it as a substitution failure and determines the signature to be unsuitable.
Furthermore, as Maxim Egorushkin pointed out, there is the possibility that T::serialize
might return a user-defined type that overloads operator,
. To ameliorate (albeit very unlikely) potential error, the code should be:
template<typename T>
struct has_serialize
{
// ...
template<typename C>
static constexpr auto test(int)
-> decltype(static_cast<void>(std::declval<C>().serialize(std::declval<SerializerBase&>())), std::true_type());
// ...
};
Clang and GCC disagree on whether overloaded function templates are ambiguous
I'm unsure which compiler that is correct, but...clang++
is correct because both functions matches equally good.
A C++11 solution could be to just add the requirement that the Rest
part must contain at least one type and that is easily done by just adding R1
. That would mean that the rest of your code could be left unchanged:
template<typename Fn, typename H, typename R1, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R1, R...>*)
{
fn(H{});
using Rest = const std::tuple<R1, R...>*;
feh<Fn, R1, R...>(fn, static_cast<Rest>(nullptr));
}
A C++17 solution would be to remove the other feh
overloads and use a fold expression:
template <typename Fn, typename... H>
inline void feh(Fn& fn, const std::tuple<H...>*) {
(..., fn(H{}));
}
This is a unary left fold over the comma operator which "unfolded" becomes:
(((fn(H1{}), fn(H2{})), ...), fn(Hn{}))
Related Topics
Under What Circumstances Is It Advantageous to Give an Implementation of a Pure Virtual Function
Use Static_Assert to Check Types Passed to MACro
Error C2228: Left of '.Size' Must Have Class/Struct/Union
Why Is Std::Move Not [[Nodiscard]] in C++20
How Visitor Pattern Avoid Downcasting
Cmake Error: "Add_Subdirectory Not Given a Binary Directory"
How to Calculate Offset of a Class Member at Compile Time
Do C++11 Compilers Turn Local Variables into Rvalues When They Can During Code Optimization
Bring Window to Front -> Raise(),Show(),Activatewindow() Don't Work
C++ Makefile on Linux with Multiple *.Cpp Files
How to Debug C++11 Code with Unique_Ptr in Ddd (Or Gdb)
Why "Long Int" Has Same Size as "Int"? Does This Modifier Works at All
What Does the |= Operator Mean in C++
Extern "C" Linkage Inside C++ Namespace
Access Child Members Within Parent Class, C++
How to Use a Constexpr Value in a Lambda Without Capturing It
Visual Studio Compiler Warning C4250 ('Class1':Inherits 'Class2::Member' via Dominance)