4

I'm trying to write a C++20 concept to express the requirement that a type have a certain method, which takes an argument, but for the purposes of this concept I don't care what the argument type is.

I've tried to write something like:

template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

however, both gcc and clang reject this, giving an error that 'auto' cannot be used in the parameter list of a requires expression this way.

An alternative would be to put the type of 'x' as a second template parameter:

template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

but then this requires TX to be specified explicitly whenever the concept is used, it cannot be deduced:

struct S { void Foo(int); };
static_assert(HasFooMethod<S>);         // doesn't compile
static_assert(HasFooMethod<S, int>);    // the 'int' must be specified

Is there any way to write a concept that allows Foo to take an argument of unspecified type?

The question Concept definition requiring a constrained template member function is very similar, but not the same: that question asks how to require that a (templated) method can take any type satisfying a given concept, while this question is about requiring that a method takes some particular type, although that type is unspecified. In terms of quantifiers, the other question is asking about (bounded) universal quantification while this one is about existential quantification. The other question's answer also does not apply to my case.

Nathan Reed
  • 3,583
  • 1
  • 26
  • 33
  • 6
    [Concepts can’t do quantifiers](https://quuxplusone.github.io/blog/2020/08/10/concepts-cant-do-quantifiers/) – chris Aug 17 '20 at 02:15
  • 2
    @chris While that blog post is literally true (concepts, in fact, cannot do qualifiers - because C++ has function overloading, so it's impossible in the general case), it's certainly possible to get a solution that's good enough to be useful for typical cases. So I really dislike the blog - since it spends all this time arguing that you can't do something, but no time at all actually... I dunno... helping. – Barry Aug 17 '20 at 02:20
  • 1
    @Barry, Yup, the article does at least provide some guidance in that direction as well toward the end. Perhaps it would have been good to include a `converts_to_anything` type technique in there, though. – chris Aug 17 '20 at 02:22
  • @chris Actually, using a "converts to anything" type here is a great idea and would solve my issue entirely, I think. If you can reopen the question, you could post that as an answer (or I can write up the answer, if you don't want to). – Nathan Reed Aug 17 '20 at 04:04
  • 2
    "*Is there any way to write a concept that allows Foo to take an argument of unspecified type?*" I've said this [many times about these kinds of concept-related questions](https://stackoverflow.com/a/61447884/734069), but *you shouldn't want to*. Constraints exist to protect some template code that will do the things specified in the constraint. At the point where you perform the constrained actions, you *know* the type you expect. So that's when you constrain the interface. – Nicol Bolas Aug 17 '20 at 05:42
  • "*this requires TX to be specified explicitly whenever the concept is used, it cannot be deduced:*" Code is read more often than it is written. And `FooCallableWith` is a much more meaningful concept than `HasFooMethod`. You don't write concepts to do static_asserts; you write concepts to constrain templates. And it is the template that's going to actually call `Foo`, and when it does so, it will do so with a specific type. – Nicol Bolas Aug 17 '20 at 05:46
  • 4
    "while this question is about requiring that a method takes some particular type, although that type is unspecified".... What does that even mean? Like, you want to check that there exists _some type_ `Y` such that `x.Foo(y)` is valid? What would be the point of that check, if you didn't know what `Y` was and couldn't call `x.Foo` anyway? – Barry Aug 17 '20 at 13:17

2 Answers2

7

Concepts are not intended to provide the kind of functionality you are looking for. So they don't provide it.

A concept is meant to constrain templates, to specify a set of expressions or statements that a template intends to use (or at least be free to use) in its definition.

Within the template that you are so constraining, if you write the expression t.Foo(x), then you know the type of x. It is either a concrete type, a template parameter, or a name derived from a template parameter. Either way, the type of x is available at the template being constrained.

So if you want to constrain such a template, you use both the type of t and the type of x. Both are available to you at that time, so there is no problem with creating such a constraint. That is, the constraint is not on T as an isolated type; it's on the association between T and X.

Concepts aren't meant to work in a vacuum, devoid of any association with the actual place of usage of the constraint. You shouldn't focus on creating unary concepts so that users can static_assert their classes against them. Concepts aren't meant for testing if a type fulfills them (which is basically what your static_assert is doing); they're meant for constraining the template definition that uses them.

Your constraint needs to be FooCallableWith, not HasFooMethod.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Answering "How do I do X?" with "Why would you want to do X?" or "You shouldn't do X" is not helpful or appreciated. – Nathan Reed Aug 17 '20 at 16:32
  • 8
    @NathanReed: My answer is "you can't." Which is true; there's no mechanism that can do that in concepts. Even your answer is flawed in that `Foo` can't be overloaded. And yes, it is helpful to know that a feature isn't present in a system for a reason. Namely, you're not expected to use the system that way, and trying to use it that way isn't actually useful. – Nicol Bolas Aug 17 '20 at 16:37
-1

Something close to this can be accomplished by defining an adapter type that can implicitly convert to (almost) anything:

struct anything
{
    // having both these conversions allows Foo's argument to be either
    // a value, an lvalue reference, or an rvalue reference

    template <typename T>
    operator T&();

    template <typename T>
    operator T&&();
};

Note that these operators do not need to be implemented, as they will only be used in an unevaluated context (and indeed, they could not be implemented for all types T).

Then, HasFooMethod can be written as:

template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
    { t.Foo(a) } -> std::same_as<void>;
};
Nathan Reed
  • 3,583
  • 1
  • 26
  • 33