How can unspecified types be used in C++20 'requires' expressions?

ぐ巨炮叔叔 提交于 2020-08-20 11:29:32

问题


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.


回答1:


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.




回答2:


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>;
};


来源:https://stackoverflow.com/questions/63443702/how-can-unspecified-types-be-used-in-c20-requires-expressions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!