There are a number of answered questions about checking whether a member function exists: for example, Is it possible to write a template to check for a function's exis
// use std::void_t in C++... 17 I think? ... instead of this:
template<class...>struct void_type { using type = void; };
template<class...Ts>using void_t = typename void_type<Ts...>::type;
template<class T, class...Args>
using hello_world_ify = decltype(
std::declval<T>().helloworld( std::declval<Args>()... )
);
template<class T, class Sig, class=void>
struct has_helloworld:std::false_type{};
template<class T, class...Args>
struct has_helloworld<T, void(Args...),
void_t<
hello_world_ify<T, Args...>
>
>:std::true_type{};
template<class T, class R, class...Args>
struct has_helloworld<T, R(Args...),
typename std::enable_if<
!std::is_same<R,void>::value &&
std::is_convertible<
hello_world_ify<T, Args...>,
R
>::value
>::type
>:std::true_type{};
live example
I'd put the above in a details
namespace, and then expose a template<class T, class Sig> struct has_helloworld:details::has_helloworld<T,Sig>{};
so someone doesn't pass a type in place of the defaulted void
.
We use SFINAE to detect if we can invoke T.helloworld(Args...)
. If the passed in signature is void(blah)
, we just detect if the call can occur -- if not, we test that the return type of T.helloworld(Args...)
can be converted into the return type of the signature.
MSVC has significant issues doing SFINAE with decltype
, so the above may not work in MSVC.
Note that has_helloworld<T, R(Args...)>
corresponds to passing in an rvalue T
, invoking helloworld
in that rvalue context passing it rvalue Args...
. To make the values lvalues, add &
. To make them const lvalues, use const&
on the types. However, this should mostly only matter in some corner cases.
For the more general case, there is no way to detect the existence of an overridden method without having a sample signature to match.
The above can be adapted to handle exact signature matches.
Amusingly, if there is a signature conflict (ie, an error would occur in the immediate context of the call), it acts as if there is no such method.
As it relies on SFINAE, errors in the non-immediate context do not trigger failure.
In C++ it impossible [so far] to take the address of an overload set: when you take the address of a function or a member function the function is either unique or it is necessary to have the appropriate pointer be chosen, e.g., by passing the pointer immediately to a suitable function or by casting it. Put differently, the expression &C::helloworld
fails if helloworld
isn't unique. As far as I know the result is that it is not possible to determine whether a possibly overloaded name is present as a class member or as a normal function.
Typically you'll need to do something with the name, however. That is, if it is sufficient to know if a function is present and can be called with a set of arguments of specified type, the question becomes a lot different: this question can be answered by attempting a corresponding call and determining its type in a SFINAE-able context, e.g.:
template <typename T, typename... Args>
class has_helloworld
{
template <typename C,
typename = decltype( std::declval<C>().helloworld(std::declval<Args>()...) )>
static std::true_type test(int);
template <typename C>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
You'd then use this type to determine if there is a member which can suitably be called, e.g.:
std::cout << std::boolalpha
<< has_helloworld<Hello>::value << '\n' // false
<< has_helloworld<Hello, int>::value << '\n' // true
<< has_helloworld<Generic>::value << '\n'; // false