Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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";
}
like image 369
user5406764 Avatar asked Oct 15 '19 12:10

user5406764


3 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;
  };
like image 170
yuri kilochek Avatar answered Nov 14 '22 09:11

yuri kilochek


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;
    };
like image 44
Quentin Avatar answered Nov 14 '22 09:11

Quentin


@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).

like image 2
Robin Davies Avatar answered Nov 14 '22 09:11

Robin Davies