19

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";
}
user5406764
  • 1,627
  • 2
  • 16
  • 23
  • 3
    Note that in C++20, you can't use `concept bool` and what follows the `->` has to be a type constraint like `std::convertible_to`. GCC trunk actually diagnoses both of these. – chris Oct 15 '19 at 12:26
  • 1
    Clang: warning: ISO C++20 does not permit the 'bool' keyword after 'concept' [-Wconcepts-ts-compat] – Scott Hutchinson Jan 31 '21 at 17:28

4 Answers4

20

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;
  };
yuri kilochek
  • 12,709
  • 2
  • 32
  • 59
  • It's cleaner to check the concept not in the main function but in the global scope. I submitted an edit to the answer, which is essentially: `template constexpr bool truly_HasFunc1 = true;` `static_assert(truly_HasFunc1);` – Dean Jun 06 '20 at 14:31
  • `static_assert(truly_HasFunc1);`, which is the second line, is my actual test. The other line defines the tester `truly_HasFunc1<>`. – Dean Jun 06 '20 at 14:38
  • @Dean, I rejected the edit because it is different enough to be a separate answer. Also, I believe you are misinterpreting MCVE in the question as the actual situation OP has. The test likely needs to be performed in a generic context and not on a concrete known type. So static assert at global scope is inappropriate either way. – yuri kilochek Jun 06 '20 at 18:27
  • I understand. I actually thought you may prefer a separate answer – I put it as an edit since your answer was perfect while mine was a trivial addition. Still, personally, I think the global static_assert test is more to the point depending on the context. I write a separate answer. – Dean Jun 07 '20 at 03:18
  • Clang: warning: ISO C++20 does not permit the 'bool' keyword after 'concept' [-Wconcepts-ts-compat] – Scott Hutchinson Jan 31 '21 at 17:28
  • @ScottHutchingson well yeah, this is Concepts TS, not the version that ended up being standardized. – yuri kilochek Feb 01 '21 at 11:30
  • How would you do this if you also want the function to have a specific signature? Say it takes an int. – Makogan May 09 '21 at 19:10
  • should use `std::convertible_to` instead of `int` in `c++20` ```cpp template concept bool HasFunc1 = requires(T t) { { t.func1() } -> std::convertible_to; }; ``` – maidamai Oct 29 '22 at 09:23
7

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

Robin Davies
  • 7,547
  • 1
  • 35
  • 50
6

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;
    };
Quentin
  • 62,093
  • 7
  • 131
  • 191
0

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 >);
Dean
  • 537
  • 4
  • 8