devoln
devoln

Reputation: 406

Check existence of operator()

I need a type trait HasCall to check validity of the folowing type instantiation for T:

template<class T> struct Caller: T
{
    using T::operator();
};

Is there a way to do this in C++14? Here is my attempt, but it doesn't work: https://godbolt.org/z/vxgJCR

EDIT

I know about SFINAE and how it works. This problem is more dificult than just checking validity of some expression. I want these asserts to pass:

struct A {void operator()(int) {}};
static_assert(HasCall<A>, "Check must work with any set of arguments.");

struct B {void operator()() {}};
static_assert(HasCall<B>, "Check must work with any set of arguments.");

struct C {template<typename... Args> void operator()(Args&&...) {}};
static_assert(HasCall<C>, "Templated operators must be detected correctly.");

struct D {};
static_assert(!HasCall<D>, "No operator() at all.");

static_assert(!HasCall<void(*)()>, "Class cannot inherit from function pointers.");

Checking validity of expression &T::operator() is not enough because it doesn't work with overloaded or template operator().

Please, check your solutions with these asserts.

This question is not a duplicate.

Upvotes: 3

Views: 291

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

No, there is no way to do this.

All solutions require knowing the signature, either exact or compatible, of the call, or rely on no overloads.

In fact any overloaded or template call operator cannot be reliably detected even if you have a known signature, as implicit cast to function pointer permits declval tests to be spoofed.

You will have to find another way around your problem, or wait for reflection.

Upvotes: 5

Radosław Cybulski
Radosław Cybulski

Reputation: 2992

Try this:

template <typename T, typename = void> struct has_operator {
    enum { value = 0 };
};
// thanks super for great suggestion!
template <typename T> struct has_operator<T, std::void_t<decltype(std::declval<T>()())>> {
    enum { value = 1 };
};
template<class T, typename = std::enable_if<has_operator<T>::value>> struct Caller: T
{
    using T::operator();
};

This works by principle of SFINAE - some errors in template instantation instead of failing whole compilation will just make compiler ignore given instantation. So first we define has_operator with value = 0, which is our "default value". Then we make specialization of the template for type T. Now we want this specialization to be picked only, when T::operator() exists. So we add second template argument and set it's default value to decltype(&T::operator()) and value = 1. We don't care for real type here, what we care is that if T::operator() exists, this will compile just fine. And compiler will select this specialization for this T. When it doesn't exist - compiler will ignore this specialization and select "default" has_operator, which has value = 0.

So now we have struct has_operator, which - when used like this: has_operator<T>::value will produce constant value 0, when T don't have operator () (with any arguments, mind you) and value 1, when has. You can use it with std::enable_if (which btw works in pretty much the same way).

The list of things, that can be applied with this technique is rather long - pretty much anything, that can make or break compilation can be used.

Upvotes: 4

Related Questions