NotAProgrammer
NotAProgrammer

Reputation: 608

Constraining class template function to accept a specific POD type

I am teaching myself the new concepts feature of C++20 and I hit a snag when I was trying to ensure that the member function that has to be present only accepts specific types. I found this question which showed me how to ensure any function can be present. Then I wanted to take it a step further by ensuring a function with specific parameters are present. I started by defining that the type T must have a function number() that returns an int

#include <concepts>

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

template <typename T>
  requires MyRequirement<T>
struct RequiresBool
{
  T t;
};

struct GoodType
{
    int number(int x) { return x; };
};

int main() 
{
    RequiresBool<GoodType> good_object;
}

but gcc gave me the following error:

<source>:7:16: error: expected primary-expression before 'int'
    7 |     { t.number(int) } -> std::same_as<int>;
      |                ^~~"

So I changed it to the following which worked.

template <typename T>
concept MyRequirement = requires (T t, int x) 
{
    { t.number(x) } -> std::same_as<int>;
};

However, when I change the function signature in GoodType to int number(double x), it still works. Sure, a double can be implicitly converted to an int and this would also work for normal functions. I could get around this by declaring (but not defining) int number(double); in GoodType which would give me an error during compilation, but this puts the responsibility on whoever writes the template class to remember to do this.

This works fine with other types, e.g.

template <typename T>
concept MyRequirement = requires (T t, std::string x)
{
    { t.number(x) } -> std::same_as<int>; //int number(int x) is now invalid
};

but the integers/doubles give me a headache.

Is some way ensure that the type of x in MyRequirement HAS to be an integer using C++20 concepts?

Upvotes: 2

Views: 214

Answers (2)

L. F.
L. F.

Reputation: 20619

A compound requirement ({ expression}) checks if the expression is well-formed, so implicit type conversions occur. An int can be implicitly converted to a double, but a std::string cannot be implicitly converted to an int.

You can use the pointer to member in a compound requirement:

template <typename T>
concept MyRequirement = requires {
    { &T::number } -> std::same_as<int (T::*)(int)>;
};

Upvotes: 3

super
super

Reputation: 12968

I'm not to familiar with concepts yet, so there might be a more idiomatic way. But this felt intuitive to me and works.

#include <type_traits>

template <typename T>
concept MyRequirement = std::is_same_v<decltype(&T::number), int (T::*)(int)>;

This covers both return type and parameter.

If it's not familiar, int (T::*)(int) is a pointer-to-member-function with signature int(int) belonging to class T.
decltype(&T::number) gets the type of a pointer to Ts member number.

Upvotes: 3

Related Questions