3

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.

devoln
  • 286
  • 2
  • 8
  • [This](https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence) might be useful – Bilentor Jun 22 '19 at 19:01
  • Related [article](https://dev.krzaq.cc/post/checking-whether-a-class-has-a-member-function-with-a-given-signature/). – Fureeish Jun 22 '19 at 19:02
  • 1
    Possible duplicate of [Is it possible to write a template to check for a function's existence?](https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence) – Gaurav Singh Jun 22 '19 at 19:24
  • You're interested in a trait that say to you that exist an `operator()` with a given signature or do you simply want to know is exist one or more `operator()`, known anything about signature? – max66 Jun 22 '19 at 19:32
  • 1
    I'm sure this is a dupe, but I'm not quite sure @GauravSingh linked to the closest question... – einpoklum Jun 22 '19 at 19:33
  • I want to check any operator() without knowing anything about its signature. And this check must work with overloaded and templated operator(). In other words must compile if and only if expression `using T::operator()` is valid. But I cannot check validity of this expression because it can be used only in context of type definition. – devoln Jun 22 '19 at 19:44
  • 1
    AFAIK that's not possible. Checking for `T::operator()` will catch any signature but not work for overload sets or templated methods. Using `std::declval()()` will work for overloads and templated methods, but only if you know the right arguments to pass in. You could make `HasCall` if you know the parameters to use in the call. – super Jun 22 '19 at 19:48

2 Answers2

4

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.

Radosław Cybulski
  • 2,876
  • 8
  • 21
  • 1
    You need to use `std::void_t` or your specialization will not match properly. – super Jun 22 '19 at 19:13
  • 2
    Even better is to use `std::void_t()())>` since that will work with function pointers as well. – super Jun 22 '19 at 19:15
  • Can you please explain? I'm using this trick every day and it works for me on gcc / clang / visual studio just fine (not that vs is all that great with templates...) – Radosław Cybulski Jun 22 '19 at 19:16
  • 1
    `decltype(&T::operator(), void())>` if you can't use C++17 (`std::void_t`). – max66 Jun 22 '19 at 19:16
  • 1
    @RadosławCybulski Have a look [here](https://wandbox.org/permlink/FT5UnMETwn6Eetf4). – super Jun 22 '19 at 19:18
  • 1
    The problem is that `decltype(&T::operator())` isn't `void`, so the second template type of `has_operator` doesn't matches `void`, so the template specialization is never used. In other words: good idea but needs a little correction. – max66 Jun 22 '19 at 19:18
  • I just checked it on clang and it works perfectly. And yes, it's used. – Radosław Cybulski Jun 22 '19 at 19:21
  • 1
    @RadosławCybulski The link i posted clearly shows that it does not work. – super Jun 22 '19 at 19:22
  • 2
    @RadosławCybulski That code does not detect anything. Clearly shown by the fact that trying to use something that doesn't have an `operator()` will result in compilation error. You could remove the second template parameter in `Caller` and get the same result. [Like so](https://godbolt.org/z/5rMtr_). – super Jun 22 '19 at 19:27
  • 5
    I know all this, I already have a macro that can check validity of any expression. But expression &T::operator() doesn't work when operator() is overloaded or templated. Also I cannot use expression std::declval()() because it must work for any set of arguments. I posted a link with my attempt https://godbolt.org/z/vxgJCR it contains static_assert's I want to pass. Line using T::operator() works with all these cases and I want a type trait that works with them too. – devoln Jun 22 '19 at 19:28
  • 1
    You're right, i'm wrong, fixed. Thank you for your insight. – Radosław Cybulski Jun 22 '19 at 19:28
4

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.

Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465
  • Does this change with Concepts? – L. F. Jun 23 '19 at 01:39
  • 1
    @l.f. no it does not. – Yakk - Adam Nevraumont Jun 23 '19 at 02:02
  • 1
    I won't pretend I understand what is happening in the code, but the solution [here](https://stackoverflow.com/a/36639013/2417774) [seems to work](https://godbolt.org/z/vez05j) (if you change slightly the metafunction to make the last static_assert pass). – llonesmiz Jun 23 '19 at 06:33
  • @llonesmiz, thank you for a link! It seems like it is possible, but I would prefer not to depend on this messy template_worm code. I'm afraid it may break unexpectedly or at least slow down compilation time. So I'm going to do without it. – devoln Jun 23 '19 at 17:12
  • @llone that wotks for.most cases up to 10 argumrnts, not all. I can craft cases where using operator () will fail after pasding the linked test, or would succeed after fsiling, or would hard error when the linked test is applied, but not when.using operator(). – Yakk - Adam Nevraumont Jun 23 '19 at 17:33