Using `void_t` to check if a class has a method with a specific signature
Asked Answered
G

3

10

At the moment, I'm using this method to check if a class has a method with a specific signature.

After attending Walter E. Brown's metaprogramming CppCon2014 talk, I started wondering if void_t could be used in this particular situation to make the code cleaner and more readable.

However I'm having trouble thinking in terms of void_t - so far I understand that void_t can help me determine at compile-time whether or not an expression is valid.

Example:

template< class, class = void >
struct has_type_data_member : false_type { };

template< class T >
struct has_type_data_member<T, void_t<decltype(T::data)>> : true_type { };

If decltype(T::type) is a valid expression, has_type_data_member<T> will be a true compile-time constant. Therefore, we are sure that T has a member field called data.

I want to use the same approach to check if a type T has a method with a particular name and a particular signature.

Let's say I want to check if T has a method called getCount() that returns int. This is what I expected to work ((Ideone.com link)):

template< class, class = void >
struct hasGetCount : false_type { };

template< class T >
struct hasGetCount<T, VoidT<decltype(T::getCount)>> 
: std::is_same<decltype(std::declval<T>().getCount()), int>::type { };

Unfortunately, the static_assert tests do not pass.

What am I doing wrong? Is it possible to use void_t in this situation?

Bonus questions:

  • How can I also check if the method signature is equal to a signature the user passes as in the original implementation?
  • I can use macros to define these kind of helper structs like this:

     DEFINE_METHOD_CHECKER(hasGetCount, getCount);
     // ...
     static_assert(hasGetCount<ClassWithGetCount>::value == true, "");
    

    Is it possible to avoid having to define a struct first then check the struct's value? I mean, is it possible to use a macro to write something like this? Example:

     static_assert(CHECK_METHOD(ClassWithGetCount, getCount)::value == true, "");
    
Ginn answered 14/10, 2014 at 16:58 Comment(2)
A id-expression naming a nonstatic member function cannot be used in a decltype.Terbium
@T.C.: I see - is there an alternative way to produce a valid/invalid expression depending on whether a type has/hasn't got a method?Ginn
T
7

First, an id-expression naming a nonstatic member function can't be used as an unevaluated operand (such as the operand of decltype). Moreover, you should check for whether the entire function call expression is well formed, not just whether there is a member called getCount:

template< class, class = void >
struct hasGetCount : false_type { };

template< class T >
struct hasGetCount<T, VoidT<decltype(std::declval<T>().getCount())>> 
: std::is_same<decltype(std::declval<T>().getCount()), int>::type { };

(Use declval<T&> if you want to check that getCount() can be called on an lvalue.)

If you just check for the existence of a getCount member, then you get a hard error if a member with that name exists but isn't callable (e.g., a data member).

Although at this point you might as well consider just using something like

template< class T >
struct hasGetCount<T, std::enable_if_t<std::is_same<decltype(std::declval<T>().getCount()), int>::value>> : std::true_type { };

instead of writing the decltype twice.

Terbium answered 14/10, 2014 at 17:25 Comment(2)
declval<T> produces an xvalue, so if you want to check if getCount() can be called on an lvalue, use declval<T&> (relevant since ref-qualifiers).Hyperbaric
SFINAE is a result of the rules for class template specialization and overload resolution involving function templates. enable_if is neither a necessary nor sufficient ingredient for it. Should you want to rewrite the test in function template form, something like template<typename T> typename std::is_same<int, decltype( std::declval<T&>().getCount() )>::type test(int); is a good start and doesn’t use enable_if.Breadstuff
R
6

You could use void_t to easily verify that the return type of getCount is convertible to int:

template< class, class = void >
struct hasGetCount : false_type { };

template< class T >
struct hasGetCount<T,
  VoidT<
    decltype(std::declval<int&>() = std::declval<T>().getCount())
  >> : std::true_type {};

(Ideone live code)

Hopefully, by the time C++17 comes out, we'll be able to do this more easily with Concepts Lite:

template <typename T>
concept bool hasGetCount = requires (T t) {
  { t.getCount() } -> int;
};
Reproduce answered 14/10, 2014 at 17:31 Comment(1)
Though this check only is correct for non-class types on the lhs of =, otherwise, you're checking assignability. Similarly, it won't work for function types and arrays (directly) as far as I see.Hyperbaric
T
0

If you also want to check for the parameters to the function and its return type, similar to what Casey suggested

template<typename... > struct Voider { using Type = void; };
template<typename... TArgs> using void_t = typename Voider<TArgs...>::Type;

template<class, typename, class = void>
struct hasGetCount : false_type { };

template< class T,  typename Ret, typename... Args>
struct hasGetCount<T, Ret(Args...), 
    void_t<decltype(std::declval<Ret&>() 
        = std::declval<T>().getCount(std::declval<Args>()...))
          >> : std::is_same<
               decltype(std::declval<T().getCount(std::declval<Args>()...)), 
               Ret>::type {};

Usage:

class A {
public:
    int member;
    int getCount() {return 0;}
};
static_assert( hasGetCount<A, int(void)>::value , "A" );
Tincher answered 20/10, 2019 at 8:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.