0

I am trying to use C++20 Concepts to constrain an interface. In this interface, I want a function signature to only use references. For some reason, I can't do this. Would someone help?

#include <concepts>

template <typename T>
concept MyInterface = requires(T t)
{
    // How do I specify a f(int&) instead of f(int) here?
    {t.f(int{})} -> std::integral;
};

struct X
{
    int f(int& i) { 
       return i; 
    }
};
static_assert(MyInterface<X>);


/*
** While tempting, this is _NOT_ a solution. **
template <typename T>
concept MyInterface = requires(T t, int& i) // Add a requirement here.
{
    {t.f(i)} -> std::integral;
};

// This will compile, despite allowing `i` to be an int. 
// When really, it should be a int&.
X::f(int i) { return i };
*/
Barry
  • 286,269
  • 29
  • 621
  • 977
Jack Lane
  • 9
  • 1
  • 1
    "*I want a function signature to only use references.*" Concepts are not base classes. You do not dictate what a function signature must be; you specify what *behavior* is required. What behavior is it that you need from this function? Why do you want it to be an error to take the parameter by value? – Nicol Bolas Apr 22 '22 at 00:21
  • Requirements in requres clause are basically checking *how it can be used*, not *how it is defined*. If you want to specify the signature, you have to perform checking on signature. Have a look at [Get types of C++ function parameters](https://stackoverflow.com/a/28509563/13456180). – VainMan Apr 24 '22 at 05:50

2 Answers2

0

I disagree with this comment, this is absolutely a solution (except you don't have to make i an int&, int suffices):

template <typename T>
concept MyInterface = requires(T t, int i)
{
    {t.f(i)} -> std::integral;
};

That is, indeed, your interface: you're passing an lvalue int into the member function f and you expect it to return some type which satisfies integral.


In order to reject callees taking int, you have to either pass some different type - like something convertible to int& instead of directly that:

template <typename T>
concept MyInterface = requires(T t, std::reference_wrapper<int> i)
{
    {t.f(i)} -> std::integral;
};

or then additionally reject the other case:

template <typename T>
concept MyInterface =
    requires(T t, int i) { {t.f(i)} -> std::integral; }
    && !requires(T t){ t.f(42); };

Passing reference_wrapper<int> is... ok. Explicitly rejecting taking int rvalues seems questionable at best.

Barry
  • 286,269
  • 29
  • 621
  • 977
-1

You can utilize std::declval to create a reference type for the constraint:

template <typename T>
concept MyInterface = requires(T t)
{
    {t.f(std::declval<int&>())} -> std::integral;
};

Note that this would still allow T::f(int) to work. To disallow it, you can add another constraint to it:

template <typename T>
concept MyInterface = requires(T t)
{
    {t.f(std::declval<int&>())} -> std::integral;
}
&& !requires(T t)
{
    {t.f(std::declval<int>())} -> std::integral;
};
Ranoiaetep
  • 5,872
  • 1
  • 14
  • 39