layout: true title: Polymorphism class: animation-fade highlightLanguage:cpp


class: middle background-color: gray background-image: url(images/IMG_4495.JPEG) background-size: cover template: inverse

.col-6[










.white[The Concept of
Polymorphism in C++]

] .col-6[.small[



https://godbolt.org/z/MKecPK3fz

1
2
3
4
5
6
7
8
9
10
11
12
13
using namespace boost::ut;
using namespace std::chrono;
auto t_0 = high_resolution_clock::now();
give_talk();
auto t_1 = high_resolution_clock::now();
auto duration = 
    duration_cast<minutes>(t_1 - t_0);
constexpr auto max_nminutes = 
    minutes{90};
expect(duration < max_nminutes) 
    << std::format(
    "Talk went longer than {} minutes",
    max_nminutes);

]]

???

https://godbolt.org/z/MKecPK3fz


This Talk is a Test

I aim at leveraging the user experience by specific measures

Supporting people who are visually impaired

Supporting people who are smarter than I am

Please give me some feedback after the talk about what was helpful for you.


background-image: url(images/2024-03-30_Amsterdam_X-T3_DSCF0516.jpg)

.col-5[

Code

https://github.com/daixtrose/cplusplus-primer/tree/main/polymorphism

] .col-2[ ] .col-5[

Slides

https://www.daixtrose.de/talks/cplusplus-primer/talks/polymorphism/presentation.html

]


background-image: url(images/2024-03-29_Amsterdam_X-T3_DSCF0382.jpg) class: impact-large

Fotos are mine

Published under .white[CC BY-SA] .image-fixed-40[]


background-image: url(images/DSC_0608_DxO.jpg) background-size: cover

It’s Very Easy To Give a Talk …

… about Concepts











Greetings from David Dunning and Justin Kruger: You can give a talk, too!


background-image: url(images/IMG_0212.JPEG) background-size: cover

Thanks

** This talk would not have happened without the support
of these people:**

.col-6[

.white[C++ UG Aachen Team]

.white[

] .col-6[

.white[Stackoverflow Users]

.white[

]

.white[Friends]

.white[

] ] —

background-image: url(images/IMG_0323.JPEG)

.white[Ready for Takeoff?]


background-image: url(images/DSC_0479.jpg) class: impact-large

What to Expect




.col-4[

Classic Approach

abstract base class

] .col-4[
] .col-4[

Modern Approach

concepts

]


background-image: url(images/IMG_9638.JPEG) class: impact

Classic Approach


Classic Approach

Define the interface via an abstract base class

1
2
3
4
5
6
7
8
class ISuperCoolFeatures {
public:
    // Pure virtual member functions
    virtual std::string coolFeature() const = 0; 
    virtual void set(std::string s) = 0; 
    // Virtual destructor
    virtual ~ISuperCoolFeatures() /* noexcept */ = default; 
};

???

In classic C++, interfaces are defined via abstract base classes from which the implementation (publicly) inherits. In this example, we have defined such an abstract class:

Instead of defining the member function body (which is possible in a regular class), the member functions of an abstract interface class are declared “pure virtual” by adding = 0 to their declaration.

Any so-called implementation of the interface publicly inherits from this abstract base class and then declares and defines member functions that match the declarations of the pure virtual functions in the abstract interface class:


1
2
3
4
5
6
class ISuperCoolFeatures {
public:
    virtual std::string coolFeature() const = 0; 
    virtual void set(std::string s) = 0; 
    virtual ~ISuperCoolFeatures() = default; 
};
1
2
3
4
5
6
7
8
9
// Liskov substitution principle: 
// public inheritance models an 'is-a' relationship 
class Impl : `public` ISuperCoolFeatures {
// ... private members and member functions omitted ...
public:
    std::string coolFeature() const `override` { /* ... */ }
    void set(std::string s) `override` { /* ... */ }
    // No need to overwrite defaulted base class destructor! 
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Impl
    : public ISuperCoolFeatures {
private:
    // Default member initializer (C++11)
    std::string s_ { "<default value>" }; 

public:
    std::string coolFeature() const `noexcept` override { 
        return s_; 
    }
    void set(std::string s) `noexcept` override {
        s_ = std::move(s);
    }
};

Use of Interfaces in Function Calls

A variable (aka instantiation) of type Impl can be passed as argument to any function that expects an argument of its interface type ISuperCoolFeatures.

In our example, we define a function which has one argument of type ISuperCoolFeatures & (a reference), and returns a std::string:

1
std::string consume(`ISuperCoolFeatures`& f);

We then pass an argument of type Impl to it, e.g. like this:

1
2
3
 // Using a concrete implementation
 `Impl` i;
 consume(i);

1
2
3
4
5
6
7
8
9
10
namespace classic {

std::string consume(ISuperCoolFeatures& f)
{
    auto answer = "42";
    f.`set`(answer); // Slideware! Never do side effects in prod! 
    return "The answer to all questions is " + f.`coolFeature`();
}

} // namespace classic
1
2
classic::Impl i;
std::cerr << classic::consume(i) << '\n';

background-image: url(images/DSC_0511_DxO.jpg)


background-image: url(images/DSC_0252.JPG) class: impact

Concepts

An Incomplete Introduction

p0734r0.pdf


Concepts


Concepts

A concept is a named set of requirements. The definition of a concept has the form

1
2
template <template-parameter-list>
concept concept-name = constraint-expression;
1
2
3
4
5
6
7
8
9
template<typename T>
concept R = requires (T i) {
    typename T::type; // T must have a member named type
    // special syntax to describe the return value 
    // of the expression in {}
    {*i} -> std::same_as<const typename T::type &>;
    // p0734r0.pdf mentions {*i} -> const typename T::type &;
    // but this gets rejected by all 3 major compilers!
};

???

Implicit SFINAE: If the expressions in the so-called requires expression are valid, then the template parameter T adheres to the constraint:


Concepts

1
2
3
4
5
6
7
 // A concept is a named requirement  
 template<typename T>
 concept C = `requires (T x) { x + x; }`;
 //          |- requires `expression` -|

 template<typename T> `requires C<T>` // <-- requires `clause`
 T add(T a, T b) { return a + b; }

Putting it together in one for a requires requires:

1
2
3
 template<typename T>
 `requires requires` (T x) { x + x; }
 T add(T a, T b) { return a + b; }

Concepts

1
2
template<typename T> concept C = 
    // `something that evaluates to true` OR a requires expression 
1
2
3
4
template<typename T> concept C = sizeof(T) > 2;

template<typename T> `requires C<T>` // requires `clause`
 T foo(T a, T b) { /* whatever */ }

or

1
2
3
template<typename T>
requires `(` sizeof(T) > 2 `)` // requires clause
T foo(T a, T b) { /* whatever */ }

Concepts

1
2
template<typename T> concept C = 
    /* `sth that evaluates to true` OR a requires expression */;

Four different ways to say the same thing

1
2
3
4
5
6
template<typename T> `requires C<T>` void f2(T t); // west requires
template<typename T> void f3(T t) `requires C<T>`; // east requires
template<`C` T> void f1(T t);
void f1(`C auto` t) {
    using T = `decltype`(t);
} 

Concepts

Omitted in this talk


From Nicolai Josuttis’ comprehensive talk at Meeting C++ 2024:

1
2
3
4
5
6
7
8
9
10
11
12
13
void add(auto & coll, auto const & val) {
    if constexpr (requires { `coll.push_back(val);` }) {
        `coll.push_back(val)`; // duplication of statement  
    } else {
        coll.insert(val); // hope for an insert dies last
    }
}

std::vector<int> coll_1;
std::set<int> coll_2;

add(coll_1, 42); // calls push_back()
add(coll_2, 42); // calls insert()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T, typename Val> concept `HasPushBack` = 
    requires (T t, Val v) { 
        { t.push_back(v) } `-> std::same_as<void>`; };

void add(auto& coll, auto const& val) {
    if constexpr (`HasPushBack<decltype(coll),` `decltype(val)>`) {
        coll.push_back(val);  
    } else {
        coll.insert(val);  
    }
} // https://godbolt.org/z/Yrr6jYGrx

std::vector<int> coll_1;
std::set<int> coll_2;

add(coll_1, 42);  // calls push_back()
add(coll_2, 42);  // calls insert()

1
2
3
4
5
6
7
void add(auto & coll, auto const & val) {
    if constexpr (requires { coll.push_back(val); }) {
        coll.push_back(val);  
    } else {
        coll.insert(val); 
    }
}
1
2
3
4
5
6
7
8
void add(auto & coll, auto const & val) {
    if constexpr (requires {           
        `{` coll.push_back(val) `} -> std::same_as<void>;` }) {
        coll.push_back(val); 
    } else {
        coll.insert(val); 
    }
}

Concepts

The C++ Standard has predefined concepts, see https://en.cppreference.com/w/cpp/named_req. Example:

1
2
3
4
5
6
template<class From, class To>
concept convertible_to =
    std::is_convertible_v<From, To> `&&`
    requires {
        static_cast<To>(std::declval<From>());
    };

The concept convertible_to<From, To> specifies that an expression of the same type and value category as those of std::declval<From>() can be implicitly and explicitly converted to the type To, and the two forms of conversion produce equal results.


Concepts - More Than One Argument

1
2
3
4
5
6
7
8
9
10
11
// Two arguments!
template<class `From`, class `To`> concept convertible_to;

// - `To` is explicitly set, like in a partial specialization
// - `From` will be assigned from `T`.
// Assignment is from left to right. 
// T is first argument to the concept.
template <std::convertible_to<`std::string`> T>
void foo(T const & x) { // east const forever, Daniel!
    std::string v = x;
}

If you want to use template<C T>, then C must have one “open” argument.


Concepts - More Than Two Arguments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <concepts>

template <typename `A`, typename `B`, typename `C`>
concept something = 
    std::semiregular<A>
    `&&` std::same_as<int, B>
    `&&` std::same_as<double, C>;

// B <- int, C <- double     
template <`something<int, double>` T> void foo (T t) { /* ... */ }

int main() {
    // A <- T <- const char*
    foo("hello");
}

background-image: url(images/20150728_114910.jpg)

.white[Concepts - a Summary]

.huge[

.col-12[ Read @Foonathan’s article
]


background-image: url(images/IMG_0321.JPEG) class: impact

Modern Approach


Instead of an abstract base class

1
2
3
4
5
6
class ISuperCoolFeatures {
public:
    virtual std::string coolFeature() const = 0; 
    virtual void set(std::string s) = 0; 
    virtual ~ISuperCoolFeatures() = default; 
};

use a constraint (expressed via a concept)

1
2
3
4
5
template <typename T> `concept` has_super_cool_features 
  = `requires`(T t, std::string s) {
    { t.coolFeature() } -> std::`convertible_to`<std::string>;
    { t.set(s) } -> std::`same_as`<void>;
  };

???

In modern C++, interfaces can also be defined via concepts, which can be used to declare what features a concrete class must have. It is slightly more powerful in what can be described.

It also has a great advantage that it is non-intrusive, i.e. it does not require a concrete class to inherit from a certain interface class. This is of advantage when you are not owner of the code of a class that you want to use and cannot change its declaration and definition.

This means that objects of completely unrelated classes can be passed as arguments to a member function (aka method) or a free function, if they all have certain member functions defined.

From a code structuring point of view, this means that the cohesion between different parts of the code is reduced.

In this example, we have defined such an interface specification as a concept


Modern Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace modern {

class Impl /* : `no inheritance` here! */ {
private:
    std::string s_ { "<default value>" }; 

public:
    std::string coolFeature() const noexcept { 
      return s_; 
    }
    void set(std::string s) noexcept { s_ = std::move(s); }
};

} // namespace modern

Modern Implementation

1
2
3
4
5
namespace modern {

class Impl /* : no inheritance here! */ {
    // ...
};

1
2
3
4
5
6
7
// Check if the class adheres to the concept 
// (i.e. has the interface we want it to have).
static_assert(`has_super_cool_features<Impl>`,
    "Impl class does not meet the requirements of "
    "the has_super_cool_features concept");

} // namespace modern

Use of Concepts in Function Calls

We declare a template function that takes arguments whose type adheres to the constraints defined by the concept:

1
std::string consume(has_super_cool_features auto& s);

and can use it in the very same way like in the classic case:

1
2
Impl i;
consume(i);

Modern consume

Content of consume_class_that_adheres_to_concept..highlight-bg[hpp]:

1
2
3
4
5
6
7
8
#include <polymorphism/has_super_cool_features.hpp>
#include <string>

namespace modern {

std::string consume(has_super_cool_features auto& s);

} // namespace modern

The implementation of this template function is .highlight-bg[not visible] at the point where consume is used (linker error).

How can we avoid to expose the implementation?


Modern consume: Splitting the Code

1
2
3
4
5
6
7
8
9
10
11
12
13
├── include
│   └── polymorphism
│       ├── `consume_class_that_adheres_to_concept.hpp`
│       ├── consume_class_with_interface.hpp
│       ├── has_super_cool_features.hpp
│       ├── i_super_cool_features.hpp
│       ├── impl_with_interface.hpp
│       └── impl_without_interface.hpp
├── src
    ├── `consume_class_that_adheres_to_concept.cpp`
    ├── `consume_class_that_adheres_to_concept.ipp`
    ├── consume_class_with_interface.cpp
    └── main.cpp

Modern consume

Content of consume_class_that_adheres_to_concept..highlight-bg[ipp]:

1
2
3
4
5
6
7
8
9
10
11
12
#include <polymorphism/consume_class_that_adheres_to_concept.hpp>
#include <string>

namespace modern {

std::string consume(has_super_cool_features auto& f)
{
    f.set("42"); // side effect == BAD
    return "The answer to all questions is " + f.coolFeature();
}

} // namespace modern

Modern consume

Content of consume_class_that_adheres_to_concept..highlight-bg[cpp]:

1
2
3
4
5
6
7
8
9
#include <consume_class_that_adheres_to_concept.`ipp`>
#include <polymorphism/`impl_without_interface.hpp`>

namespace modern {

// Explicit template instantiation `for Impl`
template std::string consume<Impl>(Impl&);

}

background-image: url(images/2019-03-16_IMG_4405.JPG) class: impact

break();
(20 minutes)


background-image: url(images/DSC_0647.JPG) class: impact

Pros & Cons


Pros & Cons

.col-6[

Classic

1
2
3
4
5
template <typename T>
concept has_super_cool_features = requires(T t, std::string s) {
    { t.coolFeature() } 
      -> std::convertible_to<`std::string_view`>;
    { t.set(`s`) } -> std::same_as<void>; };

Pros & Cons

.col-6[

Classic


Pros & Cons

.col-6[

Classic


Pros & Cons

.col-6[

Classic

] .col-6[

Modern

]

.col-12[

1
2
3
4
5
6
7
template <typename T>
concept has_member_a = requires(T t) {
    { t.a } -> std::same_as<double &>;
};

struct Bar { double a; };
static_assert(has_member_a<Bar>);

]


background-image: url(images/DSC_0641.JPG) background-size: cover

Splitting Interfaces


Pros & Cons

.col-6[

Classic


Splitting Interfaces (Classic)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class IHasSet {
public:
    virtual void set(std::string s) = 0; 
    virtual ~IHasSet() = default; 
};

class IHasCoolFeature {
public:
    virtual std::string coolFeature() const = 0; 
    virtual ~IHasCoolFeature() = default;
};

class ISuperCoolFeatures 
    : public IHasSet, public IHasCoolFeature {};

Splitting Interfaces (Modern)

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename T> 
concept has_set = requires(T t, std::string s) {
    { t.set(s) } -> std::same_as<void>;
};

template <typename T> 
concept has_cool_feature = requires(T t, std::string s) {
    { t.coolFeature() } -> std::convertible_to<std::string_view>;
};

template <typename T> 
concept has_super_cool_features = 
    has_cool_feature<T> `&&` has_set<T>;

background-image: url(images/DSC_0643.JPG) background-size: cover







Testing


Testing


Testing with Mocks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Mock {
    std::list<std::string> collectedSetArguments;
    mutable std::atomic<std::size_t> nCallsToCoolFeature { 0 };
    std::string coolFeature() const {
        `++nCallsToCoolFeature`;
        return collectedSetArguments.empty()
            ? "<default value>"
            : collectedSetArguments.back();
    }
    void set(std::string s)
    {
        collectedSetArguments.`emplace_back(std::move(s))`;
    }
};

Testing with Mocks

1
2
3
4
5
6
7
8
9
10
11
12
13
"[modern mock]"_test = [] {
    static constexpr auto EXPECTED_COOLFEATURE_CALLS = 2; 
    `mocking::Mock impl`;
    expect("<default value>"s == impl.coolFeature());
        auto result = `modern::consume(impl)`;

        expect(EXPECTED_COOLFEATURE_CALLS 
            == impl.nCallsToCoolFeature);
        expect("The answer to all questions is 42"s 
            == result);
        // side effect
        expect("42"s == impl.coolFeature()); 
    };

background-image: url(images/20140920_100420.jpg) background-size: cover

.white[Let’s Play With Our Options]


How to Enforce an Argument Constraint on a Method Parameter?

1
2
3
4
5
6
7
8
9
10
11
template<typename T>
concept has_set = requires {
    { `static_cast<void(T::*)(std::string const &)>(&T::set)` };
};

struct C1
{
    void set([[maybe_unused]] std::string const & s) {}
};

static_assert(has_set<C1>);

How to Enforce an Argument Constraint on a Method Parameter?

1
2
3
4
5
6
7
8
9
10
11
template<typename T>
concept has_set = requires {
    { static_cast<void(T::*)(std::`string` const &)>(&T::set) };
};

struct C2
{
    void set([[maybe_unused]] std::`string_view` const & s) {}
};

static_assert(`!`has_set<C2>);

How to Enforce an Argument Constraint on a Method Parameter?

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T>
concept has_set = requires {
    { static_cast<void(T::*)(std::`string` const &)>(&T::set) };
};

struct C2 
{ // `MSVC v19 fails!` https://godbolt.org/z/6PYeGv4bW
    void set([[maybe_unused]] std::`string_view` const & s) {}
    void set([[maybe_unused]] std::`string` const & s) {} 
};

static_assert(has_set<C2>);

How to Relax an Argument Constraint on a Method Parameter?

1
2
3
4
template <typename T> 
concept has_set = requires(T t, `std::string` s) {
    { t.set(s) } -> std::same_as<void>;
};

The requirement for s could be a little bit more relaxed. What are our options?

<img width=”100%” src=images/2015-07-28_Dartmoor_2.jpg>

???

https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAMzwBtMA7AQwFtMQByARg9KtQYEAysib0QXACx8BBAKoBnTAAUAHpwAMvAFYTStJg1DIApACYAQuYukl9ZATwDKjdAGFUtAK4sGEjaSuADJ4DJgAcj4ARpjE/qQADqgKhE4MHt6%2B8UkpjgIhYZEsMXFcAXaYDmlCBEzEBBk%2BfmW2mPZ5DDV1BAUR0bHxCrX1jVktQ929RSX%2BAJS2qF7EyOwc5gDMocjeWADUJutuBACeCZgA%2BgTETIQKB9gmGgCCG1s7mPuHQ8ShwOcAbnhMAB3e6PF5mTYMbZePYHNzfX5g57ggiYFgJAxoz5HU6MVgfAAqpF2JzOzDYuyEnwAIrshugQCBEUZkU80NDMAkCLsEEwFOclDyDnTiJgAI5ePBihQQQmkknUhSzfYAdisz12WrVFlJADohRBlWq6QBae70giM5kE878%2BH/VB4dBg9Yal6qmkHd3g75eBy7NxccEmdXg7W7R3O%2BmYAgQEwAVisSZYTGOMXOXgYXiULoTXvzlutLOAuw5Q32ZgAbPSVaGrJ6Q423SHnhNHMg7QolPUIHyBUL4UH7rNva2nn6A24zE33RGo%2BgY3HE8mLKn0xcsznMHmC3SGUySwCgcCywIK%2BYa8b66GvSjmz627UO12e3H%2B4LY0OZ%2BtsKOWyiT7EP6PJuOss7htqC5LvGSYruuGZbrmiZ7kWh5XL8x4gmeDAXtWtY4RWDCoJgqgrNyOq3k2XoAS8T5MC%2B/Jvn2/KfgQQ7gb%2B/6PnRE5XCBgaSBBmpQU6i6Giu8Fpoh2bIfmKG7KEPJ4Cq5Y8sRpHkcKYaNveNE8e2eCdkxsTvqxg6HG4QlcWOgG8UpuypqEEB1mGdm3hw8y0JwCa8H4HBaKQqCcG41jWPSizLB8Gw8KQBCaJ58wANYgAmGh6hoACckgaJI6wABz5aqUiqus6xVvonCSH5CVBZwvAKCAATxQFnmkHAsAwIgKACPwxCppy5CUGgGJ0LEjWwCNCRjcQACSjL/MgCQJACXCZecWKYEM5yqFW0ioNyaSNRwAD0B6mJY1ibRWJ0AFpMLsJ0AOpiLQj0APJmG1EBTTN80oAYRhrS0B0dMdpoMiKF1WJYkK7BDVq0HgUQikjUTQ%2BFpovbQb1Y6RVwPaaZzoIYHbfb99BzYywBSGYQWHeenAIy66w0hjsPrPD2O409%2BPXPDxOk0ZXPEaaWZJcRwIMET1zAKmCgi6gYuyTuRN1ASaLEPD708LM8w7oQJDOnogPAP97M2GjE0uV5Pk1a1dUcB4DB9QNKy7AAaiesS7BAuCG1rMUqusJ1cL5cUJXrpApZIVZ6qqCZVhoCZcKqVZmAmCaqvllUcNVpAsCA1Z6pIqdmNnVZlWVVZpTn/mBcFHANU1EetfMHXdRTsRDT9qCjZTAOGDT%2BUtDQtCa41EBRLVUShHUxycLFs/MMQxzvVE2iVC1sUjWwgjvQwtALw7WBRF4wBuK9x2xVgA3AOIJ/SlveD/FttWkZUXhorVSltLVaPXFXh4LAtUMKF24LwV%2BxAojJEwDSdEQ8kZGEjnwAwwAFBexBO9cki9eD8EECIMQ7ApAyEEIoFQ6gHa6C4PoIeKAwqWH0Mja28xQZHSZpDVmFs4amnepzZmaNUbMKtBbLmr0uZ80JoLQQwssZkkwKaJGLBbgCwIAgMUTB0C8FQFAn4WAWGtHaGkFwDB3CeCaHoYIoQ%2BjFAGDQnIqQBCjGaIkZIjiGBTH6KUQxz8BBdBGOYsYPiqh%2BOGD0ax0w7G2DCc4vQEx6ieNsaUeYChIorAkLbDg4d67aM4LsVQ%2BUqymj2mWU2uwuD5T1FwDKvt/ZEEDpCLgsxeAtS0O3JAiwCAJC/j3LuxBwgEk4AUopJTthD3KXHdKgUDb1ONjQ/BwhRDiBIQs8haharUNIMCa4CRcGZOybVRu70v7dJ5KgKg%2BTCnFMkKU8ZFSqk1IgB4fuPsg4tMjvMBAmBNEDBcrnfOhczCSD1OsbO6wEz5UkJIFOWd8rlVIDkx2zcaGtO%2Bl1PsPUXlkAoL3LFIBFrLVWuta6BAdp7T4HQCelBp4O2XvPXBpA6Wr3XpvBwtVd6MAIAfI%2BtVT7n0vjja%2BvBb6INWDfJ%2BVRX7HWmaoT%2B38IHkEEH/BVAD57ANWIFMBDKoEwKUPAu%2BSDQBt1QUwdBmDgTYMYAyhZhDlnSFWUodZVCi60OQRbJhUQDFsMZqdc6DCLAksevdD6X0gq6OdG/SAKS2i%2BL8BAVwsSaFWMKF4vQDiOiJtcbkNIiSZg0IqCEzoMTAkuILR0fx4SU1JLicWzIpawm5rsSktJxD9n2wbnk3aNyWAKEWpGNaepA1%2B3wPUys6wmnvONRizppzel92mpTAZbAhklJ7X2/4A6SW8BmUbRk8zZC2uIfa2QazKGBV0HTbZTBdkQLbQiw5nBjldK/rsc5%2BTV29uQP2zKg6GJbR5E8%2BdM0x1mGaa3NpmSAVFzMHqBMeVJBmHypCTKSc4UZ3vQ7RuzdmooI7hivpc68U0wQxS8e41qUzznqvBlTK14by3uyvue8uWH2PoFPlF8r4MpFUYB%2B7GJWOCle/WVyAv4at4L/byDtVVAIwOJuKPxwGxR1bA/ViDfgoKoGgjBJ5LX%2BVijapZR7SHyEdWenQIB1iuuMP6j1XqGa4Q4VaKG/qeHcwkaoAmAsdxCy/UTNRGjFy8PWNo8N%2Bj4DRqMc4eNpjM3JpsXmrN7jM3ppzRE1N%2BaY2ForZmst1QG3perfm2tFjiuTEK3m5tSx0lNNzgczDnarklOAMgL9Ug9RmFqSOkgY6J3ge%2Bh0k5PScV9KXasYZ1zdgtba%2BsKp27ut6L0IZohEhj1kLMxsyzWydl7Lq%2B23JHAn2nNfRcibzXWvlOBZ1wDWKx3rDA6iqOUmoPFyrEnTK6xY4j3yplUqCY6aIqw7YPQj3kogEkD%2BzKydMqJy4FwRD/23vSCkyFjDHam79ae5wUNhcygBEB/VTH8woEpGcJIIAA%3D%3D


How to Relax an Argument Constraint on a Method Parameter?

1
2
3
4
5
6
7
8
9
10
11
template <typename T, typename S `= std::string`> 
concept has_set = requires(T t, S s) {
    { t.set(s) } -> std::same_as<void>;
};

struct C1
{
    void set([[maybe_unused]] `std::string` const & s) {}
};

static_assert(has_set<C1>);

How to Relax an Argument Constraint on a Method Parameter?

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T, typename S = std::string>
concept has_set = requires(T t, S s) {
    { t.set(s) } -> std::same_as<void>;
};

struct C2
{
    void set([[maybe_unused]] `std::string_view` const & s) {}
};

// implicit conversion takes place
static_assert(has_set<C2>);

How to Relax an Argument Constraint on a Method Parameter?

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T, typename S = std::string>
concept has_set = requires(T t, S s) {
    { t.set(s) } -> std::same_as<void>;
};

struct C3
{
    void set([[maybe_unused]] `std::string_view` const & s) 
        `const noexcept` {}
};

static_assert(has_set<C3>);

How to Relax an Argument Constraint on a Method Parameter?

1
2
3
4
5
6
7
8
9
10
11
template <typename T, typename S = std::string>
concept has_set = requires(T t, S s) {
    { t.set(s) } -> std::same_as<void>;
};

struct C4
{
    void set([[maybe_unused]] int i) const noexcept {}
};

static_assert(`!`has_set<C4>); // TODO: Explain 

How to Relax an Argument Constraint on a Method Parameter?

1
2
3
4
5
6
7
8
9
10
11
template <typename T, typename S = std::string>
concept has_set = requires(T t, S s) {
    { t.set(s) } -> std::same_as<void>;
};

struct C4
{
    void set([[maybe_unused]] int i) const noexcept {}
};

static_assert(has_set<C4, `int`>); // Aha!

How to Relax an Argument Constraint on a Method Parameter?

1
2
3
4
template <typename T> 
concept has_set = requires(T t, std::string s) {
    { t.set(s) } -> std::same_as<void>;
};

The requirement for s could be relaxed.

1
2
3
4
template <typename T> 
concept has_set = requires(T t, `/* `what do we put here?` */` s) {
    { t.set(s) } -> std::same_as<void>;
};

How to Relax an Argument Constraint on a Method Parameter?

1
2
3
4
5
6
7
8
#include <type_traits>
#include <string_view>

template <typename T, typename S> 
concept has_set = `requires`(T t, S s) { 
    `requires` std::`convertible_to`<S, std::string_view>;
    { t.set(s) } -> std::same_as<void>;
}; // https://godbolt.org/z/nPodYz8qG

The concept convertible_to<From, To> specifies that an expression of the same type and value category as those of std::declval<From>() can be implicitly and explicitly converted to the type To, and the two forms of conversion produce equal results.


How to Relax an Argument Constraint

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename T, typename S>
concept has_set = requires(T t, S s) {
    `requires std::convertible_to<S, std::string_view>;`
    { t.set(s) } -> std::same_as<void>;
};

struct C1
{
    void set([[maybe_unused]] `std::string const &` s) {}
};


static_assert(has_set<C1, `std::string const &`>);

How to Relax an Argument Constraint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T, typename S>
concept has_set = 
    std::convertible_to<S, std::string_view>
    `&&` // <---------
    requires(T t, S s) {
        { t.set(s) } -> std::same_as<void>;
    };

struct C1
{
    void set([[maybe_unused]] std::string const & s) {}
};
// Can we get rid of the `explicit mention of set's argument?` 
static_assert(has_set<C1, std::string const &>);

How to Relax an Argument Constraint on a Method Parameter?

1
2
3
4
template <typename T>
concept has_set = requires(T t) {
    { t.set(/* `what do we put here?` */) } -> std::same_as<void>; 
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Something {
    operator std::string_view(); // declarations are sufficient!
    operator std::string();
    operator char *();
};

namespace { Something `s`{}; } // do not leak a symbol!

template<typename T>
concept has_set = requires(T t) {
    { t.set(s) } -> std::same_as<void>;
};

struct Bar { void set(std::string_view); };
static_assert(has_set<Bar>);

https://godbolt.org/z/YscW7Pjz6


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Something { // `TODO: This should be anonymous`
    operator std::string_view();
    operator std::string();
    operator char *();
};

template<typename T>
concept has_set = requires(T t) {
    // { t.set(`Something{}`) } -> std::same_as<void>;
    { t.set(`declval<Something>()`) } -> std::same_as<void>;
};

struct Bar { void set(std::string_view); };

static_assert(has_set<Bar>);

How to Relax …

1
2
3
4
5
6
7
8
9
10
11
template <typename T>
concept has_set = requires(T t) {
    { t.set(
        [](){ 
            `struct` { // hidden unnamed struct 
                operator std::string_view();
                operator std::string();
                operator char *(); } `s`; // s not visible outside 
            `return s;` }`()` // invoke body of lambda expression
      ) } -> std::same_as<void>; 
};

https://godbolt.org/z/edhjEoGGP


1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename T> concept has_set = requires(T t) {
    { t.set([](){ struct {
                operator std::string_view();
                operator std::string();
        } s; return s; }()
      ) } -> std::same_as<void>;
};

struct Bar {
    void `set(std::string_view)`;
    void `set(std::string)`;
};
// `ambiguous` - which conversion operator to choose? 
static_assert(`!`has_set<Bar>); 

https://godbolt.org/z/zsq5h8e4h


How to accept any set function?

Slideware! Does not compile.

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T>
concept has_set = requires(T t) {
    { t.set(
        [](){ // set should take `any argument`
            struct { 
                // `Compiler error`: Templates cannot be 
                // declared inside of a local class!
                `template <typename U> operator U();`
            } s;
            return s; }() 
      ) } -> std::same_as<void>;
};

How to accept any set function?

Back to external struct. Note: .primary[The ambiguity remains].

1
2
3
4
5
6
7
8
9
10
namespace { namespace internal { 
[[ maybe_unused ]] struct {
    template <typename U> operator U();
} s;
} /* namespace private */ } // namespace

template<typename T>
concept has_set = requires(T t) {
    { t.set(::internal::s) } -> std::same_as<void>;
};

https://godbolt.org/z/8z54jj8qW


Using Predefined std::Concepts?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T>
concept has_set = 
   std::invocable<decltype(&T::set), T &, std::string>
|| std::invocable<decltype(&T::set), T &, 
                  std::string_view const &>
|| std::invocable<decltype(&T::set), T &, char const *>;

struct Bar { void set(std::string_view); };
static_assert(has_set<Bar>);

struct Baz { void set(std::string); };
static_assert(has_set<Baz>);

struct Bazz { void set(char const *); };
static_assert(has_set<Bazz>);

Using Predefined std::Concepts?

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
concept has_set = 
   std::invocable<decltype(&T::set), T &, std::string>
|| std::invocable<decltype(&T::set), T &, 
                  std::string_view const &>
|| std::invocable<decltype(&T::set), T &, char const *>;

// Reminder: decltype(&T::set) may still be ambiguous  
struct Bar {
    void `set`(std::string_view);
    void `set`(std::string); 
};
static_assert(`!`has_set<Bar>); 

Splitting Concepts Allows Overloads

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T>
concept has_set = requires(T t) {
{ t.set([](){ struct { operator `std::string_view`(); } s; 
    return s; }()) } -> std::same_as<void>; }
  `||` // order matters, short circuit kicks in here
                  requires(T t) {
{ t.set([](){ struct { operator `std::string`(); } s; 
    return s; }()) } -> std::same_as<void>; };

struct Baz { void set(std::string); 
             void set(std::string_view); };
static_assert(/* hooray! */ `has_set<Baz>`);

https://godbolt.org/z/GMrdjqoE7


Pushing the Limits

“I want it all and I want it now”

1
2
3
4
5
6
template<typename T>
concept has_set = requires(T t) {
    { t.set(
        `any_of<std::string_view, std::string, char*>`() 
    ) } -> std::same_as<void>;
};

https://godbolt.org/z/ehxhxbscd


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <typename T> struct can_convert_to { 
    using type = T;
    operator T(); 
};

template<class`...` Ts> struct overloaded : `can_convert_to`<Ts>`...` { 
    using can_convert_to<Ts>::operator 
        typename can_convert_to<Ts>::type`...`; 
    };

template<typename... Ts> auto any_of() 
    { return overloaded<Ts...>{}; };

template<typename T> concept has_set = requires(T t) {
    { t.set(
        any_of<std::string_view, std::string, char*>() 
    ) } -> std::same_as<void>;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename T> struct can_convert_to { operator T(); };

template<class`...` Ts> struct overloaded 
    : `can_convert_to`<Ts>`...` { 
    using can_convert_to<Ts>::operator Ts`...`; };

template<typename... Ts> 
auto any_of() { return overloaded<Ts...>{}; };

template<typename T>
concept has_set = requires(T t) {
    { t.set(
        any_of<std::string_view, std::string, char*>() 
    ) } -> std::same_as<void>;
};

https://godbolt.org/z/Gdc3E6veT


Problem With Overloading Remains

1
2
3
4
5
6
struct Bar {
    void set(std::string_view);
    void set(std::string);
};

static_assert(`!`has_set<Bar>);

Let us look at …

fold expressions for concepts, proposed by Corentin Jabot in p2963r3.pdf.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename T, typename Arg> 
concept has_set_helper = requires(T t, Arg arg) { 
    { t.set(arg) } -> std::same_as<void>; };

template<typename T, typename`...` Args>
concept has_set_with_any_of = (has_set_helper<T, Args> `|| ...`);

template<typename T>
concept has_set = has_set_with_`any_of`<T, 
    `std::string_view, std::string, char const *`>; 

struct Bar {
    void set(std::string_view);
    void set(std::string);
};
static_assert(/* hooray again! */ `has_set<Bar>`);

Kirisame Igna Is Smarter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T, typename... Args>
concept has_set_args = 
    ((requires(T t, Args args) {
        { t.set(args) } -> std::same_as<void>;
        }) || ... || false);

template <typename T> concept has_set = 
    has_set_args<T, std::string_view, std::string, const char *>;

struct Bar {
    void set(std::string_view);
    void set(std::string);
};
static_assert(has_set<Bar>);

Homework

Find a way to express

1
2
3
4
template <typename T, typename Arg> 
concept has_set = requires(T t) { 
    { t.set(/* `anything, not a limited list of types` */) } 
    -> std::same_as<void>; };

You must not use reflection 😜.


Homework 2

Fix the code of the title page to also describe a suspend and resume with coroutines
and .highlight[give a talk about it!]

1
2
3
4
5
6
7
8
9
10
11
using namespace boost::ut;
using namespace std::chrono;

auto t_0 = high_resolution_clock::now();
give_talk(); // a pause is missing here.
auto t_1 = high_resolution_clock::now();
auto duration = duration_cast<minutes>(t_1 - t_0);
constexpr auto max_nminutes = minutes{90};
expect(duration < max_nminutes) 
    << std::format("Talk went longer than {} minutes", 
                   max_nminutes);

background-color: #222222

Homework 3

Give Feedback, please.

<img width=100% src=images/2015-07-28_Dartmoor_1.jpg>


template: roentgen class: middle background-image: url(images/DSC_0229.JPG)

.center[.white[Thank You for your attention!]]