C++ Concepts - Can I have a constraint requiring a function be present in a class?

Viewed 6994

I have a simple code snippet below, which compiles using:

g++-9 -std=c++2a -fconcepts

This is trying to define a concept that requires the presence of a function. I would expect the output to be "yes" but it's not... Any idea why? Thanks.

#include <iostream>


template <typename T>
concept bool HasFunc1 = 
    requires(T) {
        { T::func1() } -> int;
    };

struct Test
{
    int func1()
    {
        return 5;
    }
};

int main()
{
    if constexpr (HasFunc1<Test>)
        std::cout << "yes\n";
}
4 Answers

You are testing for presence of a static member function. What you want is

template <typename T>
concept bool HasFunc1 = 
  requires(T t) {
      { t.func1() } -> int;
  };

Try calling it yourself:

Test::func1();

prog.cc: In function 'int main()':
prog.cc:19:14: error: cannot call member function 'int Test::func1()' without object
   19 |  Test::func1();
      |              ^

Oh, right. func1 should either be a static member function, or you should call it on an instance inside your concept:

template <typename T>
concept bool HasFunc1 = 
    requires(T t) {
        { t.func1() } -> int;
    };

@makogan asked (buried 19 deep in comments): what if func has arguments?

The answer is: for simple cases, manufacture parameters using constructors, or new expression. (Not particularly readable, but way more readable than the probably correct way given below).

template <typename T>
concept HasFunc1 = 
  requires(T t) {
      { t.func1( int() ) } -> std::same_as<int>;
  };

For more complex examples, you can declare test parameters in the requires clause argument list:

concept IsCoServiceImplementation = requires(
    T t,
    CoServiceReply<typename T::return_type> *completionCallback)
{
    { T::return_type };
    {t.OnSuspend(completionCallback) };
    {t.OnCancel(completionCallback)  };
};

This concept does have the desired intent (for me): it mostly converts nested-10-deep error messages about failures to meet a template contract into almost readable first-level error messages.

There's still a strange disconnect between the required code and the concept. It would be really nice to have tighter constraints on parameter types. And testing const-ness is grimly difficult. Far from the feature I had hope for. :-(

I'm still struggling with c++20 features. I'm open to suggestions as to how to do this better.

(A CoService, in case you're wondering, is an experimental attempt I'm working on to make it easier to marshal coroutine code back onto non-coroutine code with minimum fuss and bother).

One can run the concept check at the compile time. (The OP's check evaluates only at the run time.)

First, prepare a checker function (technically a templated variable):

template <HasFunc1 h>
constexpr bool HasFunc1_satisfied = true;

Then do the check somewhere.

// The class to be tested
struct Test
{
    int func1()
    {
        return 5;
    }
};

// Do the test at the compile time
static_assert(HasFunc1_satisfied< Test >);
Related