Why Do My Sfinae Expressions No Longer Work with Gcc 8.2

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

clang++ is correct because both functions matches equally good. I'm unsure which compiler that is correct, but...

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



Leave a reply



Submit