Using C++ concepts for great good :) part 1
Tags: haskell, cpp, typesystems, concepts
Published: 2023-03-29 01:01:25

Motivated by Haskell’s type system I have been learning the C++20 feature, concepts. Concepts are to C++ what type classes are to Haskell (conceptually at least). In a nutshell, concepts allow us to declaratively describe a type based off of requirements that the type must satisfy. Often this is paired with C++ type_traits that aid in this description.

Why is this cool? Well, it allows us to constrain polymorphic types elegantly. This leads to better error messages, safer code, and overall tighter C++ (in my opinion). In my short example I am going to go through how to ensure that a template argument derives some notion of printing to the terminal. That is, I want to make sure any template argument passed into my container has an overloaded `std::ostream operator` `<<`. This might not be the most compelling example of concepts but I found it nifty and those who aren't experts may also.

Let’s do it!

```c++
//this is a container
template <typename T>
struct Container {
    using value_type = T; //type alias
    T val;                //storage

    void show() {
       std::cout << val; 
     }
}
```
So we start with a simple struct `Container` with a type alias `value_type` and a `show` function that simply prints the `val` member to the screen. This works as it should for the following substitutions for type `T`.

```c++
int main() {
    Container<std::string> t{"Concepts rule!"}; 
    t.show(); //prints "Concepts rule!" to the terminal
   return 0;
}
```
```c++
int main() {
    Container<int> t{69};
    t.show(); //prints 69 to the terminal
   return 0;
}
```
But as I am sure you know, this is not fine and dandy for all types passed as template arguments. Consider the example of passing in a user defined type to `Container`.

```c++
template<typename T>
struct Box { /* implementation code ... */ };

int main() {
    Container<Box<int>> t{};  //this is fine
    t.show();                 //compiler error!
   return 0;
```
This blows up in our face when we substitute a template argument for something that does not define the `<<` operator. Not at the time of template instantiation but because we call `t.show()`.

We get an error message that says,

```c++
`no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'Bar<int>'
```
This is obvious, right? The error message clearly says that the compiler has no idea what it means to apply the infix operator `<<` with your user defined type `Bar<int>` as an argument.

Well, let’s say that we want to add the constraint that **any** type that does not implement a notion of printing to the terminal is **incorrect**. In our current example, if we never call `show`, the compiler error won’t arise! This is bad. We want to ensure that at **template instantiation** we are not allowed to pass in a type that does not comply to our constraint.

This is different than a generic compiler error based on a language mechanism like operator overloading. We are declaring the “acceptable types” of `Container`.

This is where concepts come in. Concepts allow us to express ideas related to types such that a template parameter has semantic meaning. We are constraining polymorphic types.

Lets make a concept called `Showable`

```c++
template<typename T>
concept Showable = requires(T& t, std::ostream& os) {
    {os << t} -> std::same_as<std::ostream&>; 
};
```
Let’s un-pack this. We introduce the `concept` keyword that creates a new set of types called `Showable`. The requires syntax used in this example is in the form of `requires(parameter list...){requirement sequence...}`. The parameters do nothing else other than help us describe requirements in the requirements sequence. The requires clause reads as, given a `T&` and a `std::ostream&` the expression `{os << t}` returns `std::ostream&`.

Putting the whole thing together should read as, `Showable` takes a type T such that the operation of `os << t` where os is of type `std::ostream&` and t is of type `T&` is valid and returns a value of `std::ostream&`.

Our new concept creates a predicate that can be used in place of `typename` for our templated code. This predicate fails if a type passed into our template does not satisfy the above requirements. Failure in this context is much closer to what we want semantically.

From here we change our `Container` template struct to

```c++
template <Showable T> // change typename to our concept Showable
struct Container {
    using value_type = T;
    T val;

    void show() {
       std::cout << val; 
     }
};
```
```c++
int main() {
    Container<Box<int>> t; // where Box<T> does not implement an overloaded <<
   return 0;
}
```
```c++
error: constraints not satisfied for class template 'Container' [with T = Box<int>]
    Container<Box<int>> t;
    ^~~~~~~~~~~~~~~~~~~

note: because 'Box<int>' does not satisfy 'Showable'
template <Showable T>
```
Yay! We just wrote a concept! We said something about the type of types allowed as an argument to a user defined type….meta. This was a contrived example but it doesn’t take much convincing to see that this C++ feature is bad ass and super powerful.

Here is the semantically “equivalent” code in Haskell (in respect to constraining polymorphic types)

```haskell
data Container a =  Container a    -- a simple data constructor

show' :: Show(a) => a -> String    -- redefinition of show using the standard lib show function
show' = show

main = do
    putStrLn $ show' (Container "this isn't c++?!")
```

Where `Show(a)` constrains the polymorphic type applied to our `show'` function. In the above code our type `Container` does not derive from the type class `Show` which means that the above code is ill formed and results in the compiler error below

```haskell
 * No instance for (Show (Container String))
        arising from a use of show
```
We fix this issue with the following change


```haskell
data Container a =  Container a deriving(Show) -- our data constructor now derives Show

show' :: Show(a) => a -> String                -- redefinition of show using the standard lib show function
show' = show

main = do
    putStrLn $ show' (Container "this isn't c++?!")
```
Our constraint is met and now produces the output we were expecting :)

```haskell
Container "this isn't c++?!"
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ird12m9mrr4c9s21chp6.jpg)