2

There are many different tricks for checking whether a class Foo has a method named bar. For example:

  • If we care about the method signature, we build a trait around something like std::void_t<decltype(static_cast<ing(Foo::*)(char) const>(&Foo::bar))>
  • If we don't care about the signature, we can use something like std::void_t<decltype(&Foo::bar)>

If Foo has more than one method named bar, the former continues to work just fine (since at most one overload will be compatible with the static_cast), however, the latter will break, presumably because decltype(&Foo::bar) becomes ambiguous.

Is there a technique that can detect whether a class has one or more methods with a given name, regardless of the method signature(s)? Bonus points if it can be made to work in C++14.

Example Code (Godbolt link):

#include <type_traits>

template<typename T, typename = void> struct HasIntBar_t : std::false_type {};
template<typename T> struct HasIntBar_t<T, std::void_t<decltype(static_cast<int(T::*)(char)>(&T::bar))>> : std::true_type {};
template<typename T> inline constexpr bool HasIntBar = HasIntBar_t<T>::value;

template<typename T, typename = void> struct HasAnyBar_t : std::false_type {};
template<typename T> struct HasAnyBar_t<T, std::void_t<decltype(&T::bar)>> : std::true_type {};
template<typename T> inline constexpr bool HasAnyBar = HasAnyBar_t<T>::value;


////////////////////////////////////
// Test Types:
struct None {};
struct OneInt
{
    int bar(char);
};
struct OneIntInherited : public OneInt {};
struct OneDouble
{
    double bar() const;
};
struct OneDoubleInherited : public OneDouble {};
struct TwoDirect
{
    int bar(char);
    double bar() const;
};
struct TwoInherited : public OneInt, public OneDouble
{
    using OneInt::bar; // Required to avoid ambiguity
    using OneDouble::bar; // Required to avoid ambiguity
};
struct OneInheritedOneDirect : public OneInt
{
    using OneInt::bar; // Required to avoid hiding
    double bar() const;
};
struct OneInheritedOneDirect2 : public OneDouble
{
    using OneDouble::bar; // Required to avoid hiding
    int bar(char);
};

////////////////////////////////////
// Tests:
static_assert(HasIntBar<None> == false);
static_assert(HasIntBar<OneInt> == true);
static_assert(HasIntBar<OneIntInherited> == true);
static_assert(HasIntBar<OneDouble> == false);
static_assert(HasIntBar<OneDoubleInherited> == false);
static_assert(HasIntBar<TwoDirect> == true);
static_assert(HasIntBar<TwoInherited> == true);
static_assert(HasIntBar<OneInheritedOneDirect> == true);
static_assert(HasIntBar<OneInheritedOneDirect2> == true);

static_assert(HasAnyBar<None> == false);
static_assert(HasAnyBar<OneInt> == true);
static_assert(HasAnyBar<OneIntInherited> == true);
static_assert(HasAnyBar<OneDouble> == true);
static_assert(HasAnyBar<OneDoubleInherited> == true);
static_assert(HasAnyBar<TwoDirect> == true); // FAILS!
static_assert(HasAnyBar<TwoInherited> == true); // FAILS!
static_assert(HasAnyBar<OneInheritedOneDirect> == true); // FAILS!
static_assert(HasAnyBar<OneInheritedOneDirect2> == true); // FAILS!
Parker Coates
  • 8,520
  • 3
  • 31
  • 37
  • 3
    I suspect the *real* question you meant to ask is: How to check if I can call a method on a class with certain parameters, which may have overloads, but without restricting to a specific signature?". That is useful and actually solvable. – Mooing Duck May 09 '23 at 19:38
  • 1
    @n.m.: The [trick for private members](https://stackoverflow.com/q/64139547/8586227) works here too. – Davis Herring May 09 '23 at 21:08
  • @MooingDuck, no, I have a situation where I need to validate a *closed* set of *internal* types to ensure that they either have a method named bar with an exact signature *or* have no methods named `bar` at all. A `bar` method with the incorrect type represents a programmer error. Obviously, this is a weird and niche use case, but a real world one. – Parker Coates May 10 '23 at 11:54
  • @ParkerCoates: There's no technical reason to require that they have no methods named `bar` at all. – Mooing Duck May 10 '23 at 15:18

1 Answers1

5

You can take advantage of name lookup being ambiguous if it is found in more than one base. First make a struct that derives from your type T and some dummy type with a single member bar, then check if that derived type can find bar. If it can, it wasn't ambiguous since it was found in derived and wasn't in T, so T doesn't have a bar. Otherwise T has a bar:

struct dummy_bar { int bar; };

template<typename T, bool IsClass = std::is_class<T>::value>
struct HasAnyBar_t {
    static_assert(!std::is_final<T>::value, "Cannot check HasAnyBar<T> on final classes for implementation reasons");

    template<typename U>
    static auto check(int) -> decltype(U::bar, void(), std::false_type{});
    template<typename U>
    static std::true_type check(long);  // Above was ambiguous, so bar did exist in T

    struct derived : T, dummy_bar {};

    static constexpr bool value = decltype(check<derived>(0))::value;
};

template<typename T>
struct HasAnyBar_t<T, false> : std::false_type {};

template<typename T>
constexpr bool HasAnyBar = HasAnyBar_t<T>::value;

This checks for anything with the name bar: function overload sets with templates, static data members, non-static data members and member types.

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • Thank you so much! This is delightfully nasty and works great for my needs. I converted the idea to use `void_t` because I personally find that cleaner (and have a locally implemented `void_t` in my C++14 codebase): https://godbolt.org/z/xsqn11KMK – Parker Coates May 10 '23 at 12:40