Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to provide default implementations in C++ 20 using concepts?

I am experimenting the possibility of using concepts introduced in C++20 as static interfaces. So far, I am doing well except that I am unable to figure out a way to provide a "default implementation" for a concept.

For example, I have a concept named ByteBuffer, and it is stated like this:

template <typename T>
concept ByteBuffer = requires (T t) {
  { t.read_byte() } noexcept -> std::convertible_to<uint8_t>;
  { t.has_byte() } noexcept -> std::same_as<bool>;

  /* default implementation for `t.read_until(...)`? */
};

I should be able to, logically, provide a default implementation for t.read_until(...) (parameters omitted), and allow concrete implementations to override the default one. Is there a way? How can I do it?

If this is not possible, I think it is reasonable to add such an ability.

Currently, I have to resort to the CRTP to provide a facade class, and I think it more redundant than I could conceive.

like image 388
Deuchie Avatar asked Jul 23 '20 03:07

Deuchie


People also ask

What are concepts in c++20?

C++20 Concepts - a Quick Introduction Concepts are a revolutionary approach for writing templates! They allow you to put constraints on template parameters that improve the readability of code, speed up compilation time, and give better error messages. Read on and learn how to use them in your code!

What is a default implementation of an interface?

Default implementations help with that. An interface member can now be specified with a code body, and if an implementing class or struct does not provide an implementation of that member, no error occurs. Instead, the default implementation is used. Let’s say that we offer the following interface:

Is it possible to add a method with default implementation?

it is possible in C# 8.0. You can add a method with default implementation. You will have to change your target framework version to latest to use this feature. As a newbe C# programmer I was reading through this topic and wondered if the following code example could be of any help (I don't even know if this is the proper way to do it).

How to use template parameters in c++20?

You can use them for class templates and function templates to control function overloads and partial specialization. C++20 gives us language support (new keywords - requires, concept) and a set of predefined concepts from the Standard Library. In other words, you can restrict template parameters with a “natural” and easy syntax.


2 Answers

Concepts are not base classes. In fact, concepts have no explicit or implicit relation to any of their template arguments.

Concepts do precisely one thing: verify whether a particular set of template parameters is appropriate to use when instantiating a particular template. That's all.

Concepts contain a sequence of expressions and terms that ought to be valid for the given template parameters. That's all that is needed to determine if a set of template parameters is valid for a particular use, so that's all concepts provide.

If you need some kind of default functionality, you will have to use alternate C++ mechanisms to do that. The simplest being a utility function that has its own constraint:

template<typename T>
concept ByteBufferReadUntil = ByteBuffer<T> &&
  requires(T bb) //Add parameters as appropriate
  {
    { t.read_until() } noexcept -> std::same_as<bool>;
  };

template<ByteBuffer T>
  requires ByteBufferReadUntil<T>
bool read_byte_buffer_until(T &bb)
{
  return t.read_until();
}

template<ByteBuffer T>
bool read_byte_buffer_until(T &bb)
{
  //default implementation for `t.read_until(...)`
}

So whenever you want to do read_until for an arbitrary ByteBuffer, you call read_byte_buffer_until.

like image 178
Nicol Bolas Avatar answered Nov 15 '22 06:11

Nicol Bolas


Concepts are really just constraints on types, so it doesn't make sense to add a "default implementation" to a concept. Instead, what you can do is to provide a default implementation for read_until, and use that unless some ByteBuffer type provides its own implementation.

First you can write a separate concept that checks for read_until:

template <typename T>
concept Readable = requires (T t) {
 { t.read_until() } noexcept -> std::same_as<void>;  // takes no arguments, and returns void for demonstration
};

and then you can provide a default implementation for types that don't provide this function:

template <typename T>
void read_until_impl(T t) {
   std::cout << "default implementation";
}

template <Readable T>
void read_until_impl(T t) {
    t.read_until();  // call provided member function
}

and then some function that uses a ByteBuffer type could call the function like this:

template <ByteBuffer T>
void use(T t) {
    read_until_impl(t);
}

Here's a demo.

like image 23
cigien Avatar answered Nov 15 '22 05:11

cigien