Sfinae Working in Return Type But Not as Template Parameter

SFINAE working in return type but not as template parameter

You should take a look at 14.5.6.1 Function template overloading (C++11 standard) where function templates equivalency is defined. In short, default template arguments are not considered, so in the 1st case you have the same function template defined twice. In the 2nd case you have expression referring template parameters used in the return type (again see 14.5.6.1/4). Since this expression is part of signature you get two different function template declarations and thus SFINAE get a chance to work.

SFINAE works as return type, but not parameter type

std::enable_if<..., T>::type is a nested name, and as such cannot be deduced:

See [temp.deduct.type]/5:

The non-deduced contexts are:


The nested-name-specifier of a type that was specified using a qualified-id.

. . .

As a workaround, move the enable_if to a separate template argument:

template <typename T, size_t N, size_t M, typename std::enable_if<(M == 1 || N == 1), int>::type = 0>
static inline T Length(
const Matrix<T, N, M> & input)

Template parameters SFINAE not using a template argument

There needs to be a template parameter to substitute in order for SFINAE to take place. So create a dummy template parameter and a dummy trait that will take it and returns true and false respectively.

template<class T>
constexpr bool always_true = true;
template<class T>
constexpr bool always_false = false;

template<class T=void, std::enable_if_t<always_true<T>>* = nullptr>
void fn() {}

template<class T=void, std::enable_if_t<always_false<T>>* = nullptr>
void fn() {}

SFINAE does not work in a non-type template class

The default template parameter for the second check function is wrong, it should be std::bool_constant<T> and not std::bool_constant<!T>.

The call is_fs.check() has no matching function, because you're testing is_same<bool_constant<false>, bool_constant<true>> in the first overload and is_same<bool_constant<true>, bool_constant<false>> in the second overload.


Also note that right now the call is_ri.check() is ambiguous, because both check functions are valid in is_right<true>.

How does changing a template argument from a type to a non-type make SFINAE work?

Mainly because [temp.over.link]/6 does not talk about template default argument:

Two template-heads are equivalent if their template-parameter-lists have the same length, corresponding template-parameters are equivalent, and if either has a requires-clause, they both have requires-clauses and the corresponding constraint-expressions are equivalent. Two template-parameters are equivalent under the following conditions:

  • they declare template parameters of the same kind,

  • if either declares a template parameter pack, they both do,

  • if they declare non-type template parameters, they have equivalent types,

  • if they declare template template parameters, their template parameters are equivalent, and

  • if either is declared with a qualified-concept-name, they both are, and the qualified-concept-names are equivalent.

Then by [temp.over.link]/7:

Two function templates are equivalent if they are declared in the same scope, have the same name, have equivalent template-heads, and have return types, parameter lists, and trailing requires-clauses (if any) that are equivalent using the rules described above to compare expressions involving template parameters.

... the two templates in your first example are equivalent, while the two templates in your second example are not. So the two templates in your first example declare the same entity and result in an ill-formed construct by [class.mem]/5:

A member shall not be declared twice in the member-specification, ...

why return type deduction can not support SFINAE with std::is_invocable_v

This is [dcl.spec.auto]/11.

Deduced return types are not SFINAE-friendly, in the sense that querying the return type of a templated callable (function template / generic lambda / functor with templated operator()(...)) which leverages return type deduction requires instantiation of the particular specialization of the callable, as the definition of the specialization is needed in order to deduce the return type:

[dcl.spec.auto]/11 Return type deduction for a function template with a placeholder in
its declared type occurs when the definition is instantiated even if
the function body contains a return statement with a
non-type-dependent operand. [ Note: Therefore, any use of a
specialization of the function template will cause an implicit
instantiation. Any errors that arise from this instantiation are not
in the immediate context of the function type and can result in the
program being ill-formed
([temp.deduct]).  — end note ] [ Example:

template <class T> auto f(T t) { return t; }    // return type deduced at instantiation time
typedef decltype(f(1)) fint_t; // instantiates f<int> to deduce return type
template<class T> auto f(T* t) { return *t; }
void g() { int (*p)(int*) = &f; } // instantiates both fs to determine return types,
// chooses second

 — end example ]

Due to the implementation of std::is_invocable, which applies decltype on the (unevaluated) expression of invoking of the callable specialization (to find the return type), return type deduction is triggered for the specialization, which requires the specialization to be instantiated, which results in, in this case, as highlighted in the (non-normative) note above, the program being ill-formed.

Why does this code with SFINAE compiles error, even though there is a template that can match

SFINAE applies only if the invalid type or expression resulting from a use of a template parameter appears within:

  • a template parameter declaration within the same template parameter list
  • a default template argument which is used (or will be if overload resolution or class partial specialization matching selects the template)
  • for a function template, the declared type of the function (including its function parameter types, return type, or exception specification, but not when deducing a placeholder return type from return statements),
  • for a function template, its explicit(constant-expression) specifier, if any
  • for a class template partial specialization, the template arguments specified after the template name

See [temp.deduct]/8 - this is the "immediate context" rule.

Substitution of all type aliases and type alias templates happens essentially "before" template argument substitution, since [temp.alias]/2 says a use of the alias template is always equivalent to its substitution. For example, this explains why SFINAE applies to the ::type member lookup in a std::enable_if_t within a function type - it is equivalent to the written-out typedef std::enable_if<...>::type, so when this forms an invalid type, it's considered to be in the "immediate context" of the function template argument substitution. Type aliases don't actually get "instantiated" at all like function templates, class templates, and variable templates do.

When overload resolution considers the first split template, it tries to get the value of Vec_size_v<Vec<int, Const<2>>, Const<6>>, which causes an implicit instantiation of that specialization of the variable template. The evaluation of that variable template's initializer is within that variable template instantiation, not within the function template's function type, so SFINAE does not apply and the variable template has an error, even though it happened during a template argument deduction for overload resolution.

The obvious workaround, though probably not what you want, is to require the longer Vec_size<T, Type>::value instead of Vec_size_v<T, Type>.

Or you could give the primary template for vec_size_impl a static value member. But it doesn't actually need to have a numeric type: if you do

template <class T, class Type, class = void>
struct vec_size_impl
{
struct none_t {};
static constexpr none_t value;
};
// partial specialization as before

template <class T, class Type>
inline constexpr auto Vec_size = vec_size_impl<T, Type>::value;

then the same declaration of the first split would get an actual valid constant expression for its Vec_size_v use, but an invalid expression (0 + ... + Sizes) == Vec_size_v<T, Type> since there's no matching operator==. But this invalid expression is within the function template's function type, so then SFINAE can discard the function template from the overload resolution process.

SFINAE not working with member function of template class

You're missing a dependent name for the compiler to use for SFINAE. Try something like this instead:

#include <type_traits>

template<class T>
class A
{
public:
template<typename Tp = T>
typename std::enable_if<std::is_floating_point<Tp>::value, Tp>::type
foo () {
return 1.23;
}
};

int main() {
A<double> a;
a.foo();
}

If the type T is not floating point, the declaration would be malformed (no return type) and the function would not be considered for the overload set.

See it on godbolt.



Related Topics



Leave a reply



Submit