Concepts: checking signatures of methods with arguments

1.9k views Asked by At

I've been playing around with concepts. Here's a minimal example where I'm trying to create a concept based on method signatures:

template<typename T>
concept bool myConcept() {
    return requires(T a, int i) {
        { a.foo()   } -> int;
        { a.bar(i)  } -> int;
    };
}

struct Object {
    int foo()    {return 0;}
    int bar(int) {return 0;}
};

static_assert(myConcept<Object>(), "Object does not adhere to myConcept");

To my surprise writing { a.bar(int) } -> int did not work, so I resorted to adding an additional argument to the requires expression. This seems a bit strange and I was wondering if there is a way to do the same thing. Another thing that worked was using something like { a.bar((int)0) } -> int, but I find this worse.

3

There are 3 answers

0
YSC On BEST ANSWER

Concepts check expressions, and a.bar(int) is not one. By writing

{ a.foo(int) } -> int

you ask the compiler to check that the aforementioned expression has type int. Which doesn't make sense.

You did find a valid alternative; another one might be, since the type of a.bar(x) doesn't depend on x' value:

template<typename T>
concept bool myConcept() {
    return requires(T a) {
        { a.foo()   } -> int;
        { a.bar(0)  } -> int;
    };
}

struct Object {
    int foo()    {return 0;}
    int bar(int) {return 0;}
};

static_assert(myConcept<Object>(), "Object does not adhere to myConcept");
1
xaizek On

As actual value of the type doesn't matter, I'd suggest using int{} as argument. This documents the purpose of the argument somewhat better, IMO:

{ a.bar(int{})  } -> int;

Obviously this won't work with types for which there is no default constructor. In templates, one would use std::declval to work around similar issue, but here GCC errors:

error: static assertion failed: declval() must not be used!

But there is nothing to stop us from writing equivalent (but unimplemented) function to be used with concepts, like this:

#include <type_traits>

template <class T>
typename std::add_rvalue_reference<T>::type makeval();

template<typename T>
concept bool myConcept() {
    return requires(T a, int i) {
        { a.foo() } -> int;
        { a.bar(makeval<int>()) } -> int;
    };
}

struct Object {
    int foo()    {return 0;}
    int bar(int) {return 0;}
};

static_assert(myConcept<Object>(), "Object does not adhere to myConcept");
0
Martin Morterol On

6+ years later this question is still relevant but the syntax seem to have changed.

template<typename T>
concept bool myConcept() {
    return requires(T a) {
        { a.foo()   } -> int;
        { a.bar(0)  } -> int;
    };
}

Fail with:

warning: the 'bool' keyword is not allowed in a C++20 concept definition
    9 | concept bool myConcept() {
      |         ^~~~
In function 'concept bool myConcept()':
error: return-type-requirement is not a type-constraint
   11 |         { a.foo()   } -> int;
      |                          ^~~
error: return-type-requirement is not a type-constraint
   12 |         { a.bar(0)  } -> int;

demo : fail build

With the 2a/2b syntax (checked with g++12 and clang 17) :

#include <type_traits>

template<typename T>
concept  myConcept =  requires(T a) { // no bool, no () after myConcept no {} after = 
        { a.foo()   } -> std::convertible_to<int>; // if we need convertibility
        { a.bar(0)  } -> std::same_as<int>;        // if we need exact type
    };

struct Object {
    int foo()    {return 0;}
    int bar(int) {return 0;}
};

// no () after myConcept<Object>
static_assert(myConcept<Object>, "Object does not adhere to myConcept");
            
int main(){}

Demo on wandbox

If we change int bar(int) {return 0;} to double bar(int) {return 0;} the static_assert fail with:

error: static assertion failed: Object does not adhere to myConcept
   20 | static_assert(myConcept<Object>, "Object does not adhere to myConcept");
      |               ^~~~~~~~~~~~~~~~~
note: because 'Object' does not satisfy 'myConcept'
note: because type constraint 'std::same_as<double, int>' was not satisfied:
   11 |         { a.bar(0)  } -> std::same_as<int>;        // if we need exact type
      |                          ^
[...]/same_as.h:29:19: note: because '__same_as_impl<double, int>' evaluated to false
   29 | concept same_as = __same_as_impl<_Tp, _Up> && __same_as_impl<_Up, _Tp>;
      |                   ^
/[...]/same_as.h:26:26: note: because '_IsSame<double, int>::value' evaluated to false
   26 | concept __same_as_impl = _IsSame<_Tp, _Up>::value;

As expected!

Source: Can't make C++20 concept requiring bool member function