3

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?

rocambille
  • 15,398
  • 12
  • 50
  • 68
  • 4
    Well, what're you trying to test? Just if the name `xxx` exists at all (why?), or if you can invoke `xxx` with some arguments (which ones?)? – Barry Sep 28 '16 at 14:18
  • 2
    I don't think if it's possible if you don't provide additional parameters if function is overloaded. But I would love to see an answer :) – xinaiz Sep 28 '16 at 14:20
  • Related or Duplicate: [How to check if a member name (variable or function) exists in a class, with or without specifying type?](http://stackoverflow.com/q/36079170/514235) – iammilind Sep 28 '16 at 14:51
  • I think the question here, and the answers, might be more comprehensive that the question that @iammilind linked to. Maybe that should be marked as the dupe, keeping this question instead? – Aaron McDaid Sep 28 '16 at 14:55
  • This question isn't duplicate because in here OP adds possibility of overloading methods, and author of posted one doesn't. – xinaiz Sep 28 '16 at 15:00

2 Answers2

6

If all you're doing is checking if the name xxx exists within type T, then we can have the following approach that works for all non-final/union types T. We can create a new type that inherits publicly from both T and a fallback type that has a member xxx. We can access xxx unambiguously on the derived type if and only if T didn't have the member to begin with.

template <class T>
class has_xxx_impl
{
private:
    struct Fallback {
        int xxx;
    };

    struct Derived : T, Fallback { };

    template <class U>
    static std::false_type test(decltype(&U::xxx)* );
    template <class U>
    static std::true_type test(...);
public:
    using type = decltype(test<Derived>(nullptr));
};

template <class T>
struct has_xxx : has_xxx_impl<T>::type
{ };

If you want to check for more specific things - like for a T, you can call T{}.xxx(), I would do it differently.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • That's really clever, triggering a failure when the member *does* exist... – Quentin Sep 28 '16 at 14:27
  • Since the last test in OP's code if about attribute `xxx` which is not a member function, I do not think that this is what OP is looking for. – Holt Sep 28 '16 at 14:29
  • @Holt Once you know the name exists, it's not much more effort to ensure that its type is a function type. – Barry Sep 28 '16 at 14:33
  • @Holt Added that extra bit. – Barry Sep 28 '16 at 14:41
  • Also need a caveat about unions too. And you hit the "overloaded function" case if `T::xxx` is inaccessible. – T.C. Sep 28 '16 at 15:08
  • @T.C. Well that sucks. – Barry Sep 28 '16 at 15:17
  • Also, 1) you need to remove pointer for static member functions, and I don't see the point of remove_cv there; 2) this treats all members that you can't take the address of as "overloaded functions": enumerators, bit-fields, types. – T.C. Sep 28 '16 at 21:00
  • @T.C. Just going to revert to what I had originally, which at least is definitely right for non-class/union types. – Barry Sep 28 '16 at 21:05
3

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>;
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524