wasthishelpful wasthishelpful - 2 months ago 16
C++ Question

SFINAE to determine if a type has a potentially overloaded method

I was looking for an SFINAE solution to check at compile time if a type has a method. My goal is to check if a type is a valid "duck type", but instead of a useless compile error, I want to use

static_assert
to provide an informative message.

I found [this question], which provides a fairly good answer to my problem, except it fails when the type provides overload to the method:

template<typename...> // parameter pack here
using void_t = void;

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

template<typename T>
struct has_xxx<T, void_t<decltype(&T::xxx)>> :
std::is_member_function_pointer<decltype(&T::xxx)>{};


This works fine with the following example, and differentiate method and member variable:

struct Foo { int xxx() {return 0;}; };
struct Foo2 {};
struct Foo3{ static double xxx;};
double Foo3::xxx = 42;

int main() {
static_assert(has_xxx<Foo>::value, "");
static_assert(!has_xxx<Foo2>::value, "");
static_assert(!has_xxx<Foo3>::value, "");
}


Original live demo

The code fails if there is an overload:

struct Foo { int xxx() {return 0;} void xxx(int){} };

int main() {
static_assert(has_xxx<Foo>::value, "");
}


Failing live demo with overloaded method

How can this code be improved to handle overloading?

Answer

A proper duck type check is "can your xxx be invoked with a specific signature, and its return value used in a certain context". Having an overloaded xxx is not useful, because it matters how it is used.

We start with can_apply

namespace details {
  template<template<class...>class Z, class, class...>
  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...>;

which tells you if given arguments can be legally passed to some template.

We then express the duck type we want:

template<class T>
using xxx_result = decltype( std::declval<T>().xxx() );

template<class T>
using can_xxx = can_apply< xxx_result, T >;

and can_xxx<T> is truthy or falsy depending on if we can do a t.xxx() or not.

If we want a type restriction, we just:

template<class T, class R>
using xxx_result_as_R = decltype( R(std::declval<T>().xxx()) );
template<class T, class R>
using can_xxx_as_R = can_apply< xxx_result_as_R, T, R >;

so if you want xxx to return something int-able, we get:

template<class T>
using valid_xxx = can_xxx_as_R<T, int>;
Comments