Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE remove function from overload set if a free function does / does not exist

Tags:

c++

c++11

What is the canonical way to remove a template function from the overload set if a particular free function does not exist. So I have a function

template <typename T> void foo( T t )
{
    // Do stuff
}

and I want to remove this from the overload set if a free function taking a parameter of type T, say

bar( T )

does not exist.

Also how about the inverse remove a function from the overload if a free function does exist. So remove the function foo above if the function bar does exist?

like image 487
goneskiing Avatar asked Nov 16 '16 14:11

goneskiing


3 Answers

Simple expressions like that one can be easily SFINAE'd with decltype:

template <typename T>
auto foo( T t ) -> decltype(bar(t), void())
{
    // Do stuff
}
like image 99
Quentin Avatar answered Oct 25 '22 16:10

Quentin


namespace details {
  template<template<class...>class Z, class, class...Ts>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{};
};
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;

template<class T>
using bar_r = decltype( bar( std::declval<T>() ) );

template<class T>
using can_bar = can_apply<bar_r, T>;

this gives us a trait can_bar which is true iff bar(t) is a valid expression.

template<class T>
  std::enable_if_t< can_bar<T>{}, int> =0
>
void foo( T t ) {}

which has the advantage that the SFINAE does not mess up the foo function apparent signature. The inverse is easy:

template<class T>
  std::enable_if_t< !can_bar<T>{}, int> =0
>
void foo( T t ) {}

another advantage of this technique.

Note that you might want to can_bar<T&> instead of T if you are calling it in an lvalue context instead of forwarding it.


Some of the above is beyond C++11.

It uses a C++14 feature of enable_if_t. Replace enable_if_t<blah> with typename enable_if<blah>::type in C++11, or write your own enable_if_t.

It also uses a C++14 feature of std::void_t.

template<class...>struct voider{using type=void;};
template<class...Ts>using void_t = typename voider<Ts...>::type;

to write it in C++11.

is_detected, which on track for C++20, is similar to can_apply above.

like image 35
Yakk - Adam Nevraumont Avatar answered Oct 25 '22 16:10

Yakk - Adam Nevraumont


template <typename T, typename = decltype( bar( std::declval<T>() ), void() ) >
void foo( T t )
{
    // Do stuff
}
like image 26
sp2danny Avatar answered Oct 25 '22 17:10

sp2danny