Issue
I would like to detect if a function (operator()
in my case) is present in a class, regardless of its signature or whether it would be possible to get a pointer to it (may be impossible without additional info because it is templated or overloaded). The following code using a concept compiles on MSVC and clang, but not GCC (see godbolt link below for error messages). Is this supposed to work and is GCC not conformant, or is this not supposed to work and are MSVC and clang too lenient? It is interesting to note GCC fails not only for the overloaded and templated operator()
s, but also for the simple functor.
Note also that while the example code uses variations on unary functions taking an int
, I'd like the concept to work regardless of function signature (and its does for MSVC and clang).
Try here for GCC, clang and MSVC.
Context is making this work, it does now on MSVC and clang, but not GCC.
template <typename C>
concept HasCallOperator = requires(C t)
{
t.operator();
};
struct functor
{
int operator()(int in_)
{ return 1; }
};
struct functorOverloaded
{
int operator()(const int& in_)
{ return 1; }
int operator()(int&& in_)
{ return 1; }
};
struct functorTemplated
{
template <typename... T>
int operator()(const T&... in_)
{ return 1; }
};
template<HasCallOperator T>
struct B {};
int main()
{
B<functor> a;
B<functorOverloaded> b;
B<functorTemplated> c;
}
Solution
First, the way to check a concept is just to static_assert
(not to try to instantiate a constrained class template).
static_assert(HasCallOperator<functor>);
static_assert(HasCallOperator<functorOverloaded>);
static_assert(HasCallOperator<functorTemplated>);
Second, you can't write t.operator()
for the same reason that you can't write f.fun
for any other non-static member function: if you do class member access, it must end with invocation. So this is simply a clang/msvc bug that it allows any of this.
And then &C::operator()
will not work if the call operator is overloaded or a function template (or both).
Which really calls into question the whole point of this, since without reflection we're highly limited in the kinds of answers we can give to these questions. You can really only address the simple case of non-overloaded, non-template call operator.
Nevertheless, there is an approach that works here. The trick is the same problem we have in our present case: &C::operator()
doesn't work if operator()
is overloaded.
So what we do instead is construct a case where &C::operator()
would be overloaded if there were one, and invert the check. That is:
#include <type_traits>
struct Fake { void operator()(); };
template <typename T> struct Tester : T, Fake { };
template <typename C>
concept HasCallOperator = std::is_class_v<C> and not requires(Tester<C> t)
{
&Tester<C>::operator();
};
HasCallOperator<C>
doesn't check C
, it checks a type that inherits from both C
and a type that has a non-overloaded non-template call operator. If &Tester<C>::operator()
is a valid expression, that means it refer to &Fake::operator()
, which means that C
did not have one. If C
had a call operator (whether it's overloaded or a template or both or neither), then &Tester<C>::operator()
would be ambiguous.
The is_class_v
check is there to ensure that stuff like HasCallOperator<int>
is false
rather than ill-formed.
Note that this won't work on final
classes.
Answered By - Barry Answer Checked By - David Goodson (WPSolving Volunteer)