cigien
cigien

Reputation: 60430

sfinae on function template instantiation

I would like to check whether a function template can be instantiated for a given type. As an example, for the template:

template<typename T> void f() { T{}; }

I would like to assert that f<int> is instantiatable, and f<S> is not, where S is some type that would cause the definition of f to fail compilation if instantiated, say:

struct S { ~S() = delete; };

Clearly, if I know what the function body contains, I could write separate checks for each of the statements. However, this approach will not work if the body itself is unknown.

It appears that I can't use the usual sfinae approach to do this, as the function body isn't checked for deduction failures. Here's the attempt to play with.

Indeed, temp.deduct.8 seems to explicitly disallow this:

Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure.

and the reason for the "immediate context" constraint seems to be in the very next bullet point:

Note: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements.

If I'm understanding this correctly, and doing this is not possible, can I get an explanation for why this constraint exists? I think arbitrary expressions can be checked for substitution failure by users, so why would this be too much for an implementation?

If I'm misunderstanding this, and it is possible, could I have a solution to this problem?

Upvotes: 0

Views: 469

Answers (2)

xmh0511
xmh0511

Reputation: 7369

As said in your question,SFINAE only occurs in "immediate context" and what situation subsumed by "deduction fails" are listed in [temp.deduct]. The error happened in you code will be ill-formed,which is mentioned in the standard:

The substitution into types and expressions can result in effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the “immediate context” and can result in the program being ill-formed.

So,your program would be ill-formed when require instantiation of function template f for type S.

Solution:

#include <iostream>
#include <type_traits>

template<typename T> void f() { T a{}; }

struct S { ~S() = delete; };
struct Test{};

template<typename T,typename U = void>
struct is_sutable_type_for_function:std::false_type{};

template<typename T>
struct is_sutable_type_for_function<T,std::void_t<decltype(T{}.~T())>>:std::true_type{};

int main()
{
   std::cout<< is_sutable_type_for_function<S>::value<<"\n";
   std::cout<< is_sutable_type_for_function<Test>::value<<"\n";
}

Now,the code will work for whether the type T could be used for function template f

Upvotes: 1

AndyG
AndyG

Reputation: 41220

In C++ you must put your constraints on the function itself for detection to work:

Demo (C++20)

#include <type_traits>

template<typename T> void f() requires std::is_constructible_v<T> { T{}; }

template<class T, class = void>
struct can_call_f : std::false_type{};

template<class T>
struct can_call_f<T, std::void_t<decltype(f<T>())>>: std::true_type{};

template<class T>
constexpr bool can_call_f_v = can_call_f<T>::value;

struct S { ~S() = delete; };
int main()
{
  static_assert(can_call_f_v<int>);
  static_assert(!can_call_f_v<S>);
}

Recall that C++ works on types and the body of a function is not considered part of the function's type.

Upvotes: 0

Related Questions