Specifying a Concept for a Type That Has a Member Function Template Using Concepts Lite

Specifying a concept for a type that has a member function template using Concepts Lite

Let's think about the requirements you want from your comment:

// HKT<T> needs to have a member function template that 
// returns HTK<U> where the type U is to be deduced and
// it can be any type (it is unconstrained)

While Concepts requires us to base our constraints around concrete types, we can be smart in our selection of which concrete types we use. What do you mean by U is any type. Really any type at all, whatsoever? Think about the smallest possible set of constraints you have on U and let's build a type that satisfies them. This is known as an archetype of U.

My goto first thought for "any type" would actually be a semiregular type. A type that is default constructible, copyable, and assignable. All the normal goodies:

namespace archetypes {
// private, only used for concept definitions, never in real code
struct Semiregular { };
}

archetypes::Semiregular is a concrete type, so we can use it to build a concept:

template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF =
requires(HKT<T> h, archetypes::Semiregular r) {
{h.F(r)} -> HKT<archetypes::Semiregular>
};

archetypes::Semiregular is a private type. It should not be known to HKT, and so if h.F(r) is well-formed and returns a type convertible to HKT<archetypes::Semiregular>, it's almost certainly a member function template.

The question then is, is this a good archetype? Do we need U to be semiregular, or would irregular types work too? The fewer operations that you need, the fewer should be present in your archetype. Maybe all you need is that U is movable:

namespace archetypes {
// private, only used for concept definitions, never in real code
struct Semiregular { };

struct Moveable {
Moveable() = delete;
Moveable(Moveable&& ) noexcept(false);
Moveable(Moveable const& ) = delete;
~Moveable() = default;

Moveable& operator=(Moveable const& ) = delete;
Moveable& operator=(Moveable&& ) noexcept(false);
};
}

template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF =
requires(HKT<T> h, archetypes::Moveable m) {
{ h.F(m) } -> HKT<archetypes::Moveable>
};

We're testing the same idea - invoking F() with a type that isn't well-known and excepting the return type to reflect that, hence requiring it to be a function template. But now we're giving less functionality to the type. If F() works on any, it'll work on archetypes::Moveable.

Keep iterating on this idea until you've really pared down the required functionality to the bare minimum. Maybe you don't even need the archetype to be destructible? Writing archetypes is hard, but in cases like this, it's important to get right.

Enable member functions of class templates using concepts

In both the Concepts TS and C++20 designs, functions have an optional trailing requires-clause. So you don't need to make your member function a template to constrain it:

void MemFun() requires Fooable<T>;

Concept for member function taking a template parameter in C++20

As far as I am aware, it is not possible to test for a template member function without evaluating any of the template parameters.

That said, if you have an idea of what the classification of inputs are allowed to be -- such as if a function can only be evaluated with integral-values or something like this -- then you can test it with an explicit instantiation which may be 'good enough' for whatever your purposes are:

template<typename U>
concept CREATOR=requires(U val) {
// Checks that 'val' has a 'fct' function template that works with integers
val.template fct<int>(5);
};

Live Example

In most cases, however, its generally more useful for a concept to be defined around the complete atomic definition required to fulfill its contract -- at which point its generally better to punt this evaluation off to a template argument as well:

template <typename T, typename U>
concept fnc_evaluatable = requires(T val, U in) {
val.template fct<U>(in);
};

And then use this in greater compositions.

At this point, the state of fnc being a template also becomes less important than the state of it having a member function call fct that is callable by a U -- and can likely be simplified to just:

template <typename T, typename U>
concept fnc_evaluatable = requires(T val, U in) {
val.fct(in);
};

Specifying a concept for an object with a member function that returns a constrained value

A concept is not a type, so it can’t appear as a container element type—neither in the type of an object (this is why you have to use std::vector<std::any> to approximate std::vector<std::copyable>) nor in the type for your concept ContainerOf. Moreover, you can’t use a concept as a template argument, so you can’t have a higher-order ContainerLike concept.

What you can do is make a Container concept that checks only for empty, add the constraint

{ *c.cbegin() } -> IsStatus;

and apply it to t.getStatus() in another concept.

Type requirement in C++ concepts (C++20)

This:

template<typename T>
concept ContainsInner = requires
{
typename T::inner;
};

is requiring that T has a type named inner. Which gcc tells you in its error:

source>:6:12: note: the required type 'typename T::inner' is invalid
6 | typename T::inner;
| ~~~~~~~~~^~~~~~~~~

Q doesn't have a type named inner. If what you want is to have a member variable named inner, then you want:

template<typename T>
concept ContainsInner = requires(T t) {
t.inner;
};

Note that this doesn't even check what type it is, just that it exists. Which isn't very useful. Maybe you want to require that it's an int:

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

C++ Concept that requires a member function with an OutputIterator as parameter

This is a common problem of asking the wrong question of a concept, where you're trying to use them like you would a base class interface. With base classes, you're declaring the exact, specific functions that the derived classes are to implement. You want the user to implement exactly the function you say they must.

With concepts, you approach the issue from the other direction: what usage are you trying to create?

At some point in your code, you have some object, some iterator, and a size. And you're going to take that object, call a function by passing it the iterator and the size, and you expect a response back of a certain type. And this process has some meaning.

Then that is your concept. It's a concept that is based on at least 2 parameters: the type of the object and the iterator type. So that's what you should create.

If you have this BulkReadable constraint, then you must have some interface constrained on it. An interface that's going to call read. To call read, that interface must have an iterator.

So here are the options:

  1. The interface is given an iterator type (directly or indirectly) by the user. If that's the case, then you just use that type in the function's BulkReadable constraint. If the iterator type is based on a complex set of operations on parameters, then you'll have to do some computations to compute the iterator type.

  2. The iterator is statically determined. Then just use the known iterator type in the constraint.

The point being that, at the point where you're going to try to call read, you know what the iterator type is. And therefore, you can constrain things using that type. Your concept therefore is not really BulkReadable, but BulkReadableFrom.

In short, you shouldn't want to constrain a type on being able to take any type (or any type within some constraints). Check constraints against the actual types you're going to use them with, preferably at the point where they become relevant.

Concept definition requiring a constrained template member function

What you're looking for is for a way for the compiler to synthesize an archetype of Surface. That is, some private, anonymous type that minimally satisfies the Surface concept. As minimally as possible. Concepts TS doesn't currently allow for a mechanism for automatically synthesizing archetypes, so we're left with doing it manually. It's quite a complicated process, since it's very easy to come up with archetype candidates that have way more functionality that the concept specifies.

In this case, we can come up with something like:

namespace archetypes {
// don't use this in real code!
struct SurfaceModel {
// none of the special members
SurfaceModel() = delete;
SurfaceModel(SurfaceModel const& ) = delete;
SurfaceModel(SurfaceModel&& ) = delete;
~SurfaceModel() = delete;
void operator=(SurfaceModel const& ) = delete;
void operator=(SurfaceModel&& ) = delete;

// here's the actual concept
void move_to(point2f );
void line_to(point2f );
void arc(point2f, float);
// etc.
};

static_assert(Surface<SurfaceModel>());
}

And then:

template <typename T>
concept bool Drawable() {
return requires(const T& t, archetypes::SurfaceModel& surface) {
{ t.draw(surface) } -> void;
};
}

These are valid concepts, that probably work. Note that there's a lot of room for even more refinement on the SurfaceModel archetype. I have a specific function void move_to(point2f ), but the concept just requires that it's callable with an lvalue of type point2f. There's no requirement that move_to() and line_to() both take an argument of type point2f, they could both take complete different things:

struct SurfaceModel {    
// ...
struct X { X(point2f ); };
struct Y { Y(point2f ); };
void move_to(X );
void line_to(Y );
// ...
};

This kind of paranoia makes for a better archetype, and serves to illustrate how complex this problem could be.

c++20 concepts: How can I use a type that may or may not exist?

This has had a solution since C++98: a traits class. Concepts just makes it a bit easier to implement:

template<typename T>
struct traits
{
using type = default_type;
};

template<has_type T>
struct traits<T>
{
using type = T::my_type;
};

Without concepts, you'd need to use SFINAE to turn on/off the specializations based on whether the type has the trait or not.



Related Topics



Leave a reply



Submit