Picaud Vincent
Picaud Vincent

Reputation: 10982

std::is_invocable syntax for testing existence of an arbitrary method (not only operator())

In C++17 I know that I can write:

#include <type_traits>

struct A
{
  size_t operator()(double x) const { return 1; };
};

int main()
{
  static_assert(std::is_invocable_r_v<size_t, A, double>);  
}

However now I want to use std::is_invocable to test the existence of an arbitrary method (here the size(double) method):

#include <type_traits>

struct A
{
  size_t size(double x) const { return 1; };
};

int main()
{
   static_assert(std::is_invocable_r_v<size_t, ???, double>);  
}

The question is how one must fill the "???" to make it works ?

Upvotes: 1

Views: 2629

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275740

:

#define RETURNS( ... ) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

#define METHOD( ... )  \
    []( auto&& self, auto&&...args ) \
    RETURNS( decltype(self)(self).__VA_ARGS__( decltype(args)(args)... ) )

struct A
{
  std::size_t size(double x) const { return 1; };
};

int main()
{
   static_assert(std::is_invocable_r<std::size_t, decltype(METHOD(size)), A&, double>{});  
}

and :

#define RETURNS( ... ) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

#define METHOD( ... )  \
    []( auto&& self, auto&&...args ) \
    RETURNS( decltype(self)(self).__VA_ARGS__( decltype(args)(args)... ) )

auto call_size = METHOD(size);
struct A
{
  std::size_t size(double x) const { return 1; };
};

int main()
{
   static_assert(std::is_invocable_r<std::size_t, decltype(call_size), A&, double>{});  
}

(note: I use ... because macros don't know of <,> style brackets and commas)

Upvotes: 2

Barry
Barry

Reputation: 303357

Use the detection idiom:

template <typename T, typename... Args>
using call_size_t = decltype(std::declval<T>().size(std::declval<Args>()...);

template <typename R, typename T, typename... Args>
using is_size_callable = is_detected_convertible<R, call_size_t, T, Args...>;

static_assert(is_size_callable<size_t, A, double>::value);

This has the benefit of working with member functions size that are overloaded, templates, or take default arguments as well.


In C++20 with concepts:

template <typename T, typename R, typename... Args>
concept is_size_callable = requires (T t, Args... args) {
    { t.size(std::forward<Args>(args)...) } -> std::convertible_to<R>;
};

static_assert(is_size_callable<A, size_t, double>);

I flipped the arguments to put T first, since this would allow the type-constraint syntax:

template <is_size_callable<size_t, double> T>
void foo(T );

foo(A{});

Upvotes: 5

Picaud Vincent
Picaud Vincent

Reputation: 10982

I just realized that the right syntax is:

static_assert(std::is_invocable_r_v<size_t, decltype(&A::size), A, double>); 

Here is a working example

// -*- compile-command: "g++ -std=c++17 demo.cpp -o demo"; -*-

#include <type_traits>

struct A
{
  size_t operator()(double x) const { return 1; };
  size_t size(double x) const { return 1; };
};

int main()
{
  static_assert(std::is_invocable_r_v<size_t, A, double>);  

  static_assert(std::is_invocable_r_v<size_t, decltype(&A::size), A, double>); 

  return EXIT_SUCCESS;
}

Upvotes: 1

Related Questions