SlideShare a Scribd company logo
1 of 35
Download to read offline
concepts (since C++20)
Create "interfaces" for your templates
platis.solutions
©
for
GRCPP
Meetup
About me
Grew up in Rodos, Greece
Software Engineering at GU & Chalmers
Working with embedded systems
Teaching
DIT113, DAT265, Thesis supervision
C++, Coursera, Udemy
Open source projects
https://platis.solutions
https://github.com/platisd
Email: dimitris@platis.solutions
platis.solutions
©
for
GRCPP
Meetup
Requirements on template arguments
C++20 introduces constraints
Specify requirements on template arguments
Seamless selection of the appropriate overload or specialization
Named sets of such requirements are called concepts
Constraints and concepts
requires expression
platis.solutions
©
for
GRCPP
Meetup
Why do we need concepts?
template <typename Camera>
class AutonomousCar {
Camera mCamera;
public:
// ... A lot of code
};
How do we ensure that Camera has all required functions?
Normally, we read the Camera interface, with templates we can't.
platis.solutions
©
for
GRCPP
Meetup
Why do we need concepts?
template <typename T>
T getMedianNumber(std::vector<T> values) {
std::sort(values.begin(), values.end());
return values[values.size() / 2];
}
What kind of types does it make sense for getMedian accept?
What kind of types can getMedian accept?
platis.solutions
©
for
GRCPP
Meetup
Let's make our getMedianNumber more explicit
template<typename T>
T getMedianNumber(std::vector<T> values) {
static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>,
"T must be an integral or floating-point");
std::sort(values.begin(), values.end());
return values[values.size() / 2];
}
static_assert is great, but can make things more readable?
platis.solutions
©
for
GRCPP
Meetup
Let's make our own constraint
template<typename T>
requires std::integral<T> || std::floating_point<T>
// requires std::is_integral_v<T> || std::is_floating_point_v<T>
T getMedianNumber(std::vector<T> values) {
std::sort(values.begin(), values.end());
return values[values.size() / 2];
}
std::vector<std::string> files{"file22.txt", "file11.txt", "file33.txt"};
std::cout << getMedianNumber(files) << std::endl; // Compilation error
std::vector numbers{0, 9, 5, 7, 3, 6, 2, 8, 1, 4, 10};
std::cout << getMedianNumber(numbers) << std::endl; // 5
"No operand of the disjunction is satisfied"
requires std::integral<T> || std::floating_point<T>
platis.solutions
©
for
GRCPP
Meetup
Let's make our own concept
template<typename T>
concept Number = std::integral<T> || std::floating_point<T>;
// concept Number = std::is_integral_v<T> || std::is_floating_point_v<T>;
template<typename T>
requires Number<T>
T getMedianNumber(std::vector<T> values) {
std::sort(values.begin(), values.end());
return values[values.size() / 2];
}
Something that is satisfied or not, often treated "like a boolean"
Use concepts in requires clauses or to compose other concepts
std::integral and std::floating_point are built-in concepts
platis.solutions
©
for
GRCPP
Meetup
Trailing requires syntax is also possible:
template<typename T>
T getMedianNumber(std::vector<T> values)
requires Number<T> // <--- Trailing requires
{
std::sort(values.begin(), values.end());
return values[values.size() / 2];
}
It is exactly the same as the previous example:
template<typename T>
requires Number<T> // <--- Leading requires
T getMedianNumber(std::vector<T> values) {
std::sort(values.begin(), values.end());
return values[values.size() / 2];
}
platis.solutions
©
for
GRCPP
Meetup
Let's make getMedianNumber more readable
template<typename T>
concept Number = std::integral<T> || std::floating_point<T>;
template<Number T>
T getMedianNumber(std::vector<T> values) {
std::sort(values.begin(), values.end());
return values[values.size() / 2];
}
Use a concept as a non-type template parameter
Highly expressive and readable
Constrain the template parameter is a way that feels intuitive
platis.solutions
©
for
GRCPP
Meetup
concept vs requires
template<Number T>
requires std::is_integral_v<T> || std::is_floating_point_v<T>
T getMedianNumber1(std::vector<T> values) { /* ... */ }
template<typename T>
requires Number<T>
T getMedianNumber2(std::vector<T> values) { /* ... */ }
requires used to express requirements on template arguments
A concept is a named set of requirements
A concept is to a requires what a function is to a statement
In getMedianNumber2 we named the requirements Number
platis.solutions
©
for
GRCPP
Meetup
How do you requires ?
template<typename T>
concept Motor = requires(T m) { // <--- `requires` with curly braces
m.start();
m.stop();
};
template<typename T>
requires Motor<T> // <--- `requires` without curly braces
class Car {
// ...
};
Without curly braces: requires <some boolean expression>
With curly braces: requires(T m) { statements...; }
platis.solutions
©
for
GRCPP
Meetup
requires without curly braces
template<typename T>
requires std::is_constructible_v<T, std::string, int>
void createWithStringAndInt() { /* ... */ }
Expects a boolean expression to follow
If the boolean expression is true , requires is satisfied and valid
If the expression is false , requires is ill-formed
No error is generated if requires is ill-formed
May also be with parentheses: requires ( ... )
platis.solutions
©
for
GRCPP
Meetup
requires with curly braces
template<typename T>
concept StringAndIntConstructible = requires(std::string s, int i) {
T{s, i};
};
Expects a block of statements to follow {within curly braces}
Optionally preceded by objects for statement formulation
After type substitution if statements valid, requires is true
If any statement is ill-formed, requires evaluates to false
No error is generated if any statement is ill-formed
platis.solutions
©
for
GRCPP
Meetup
"Interfaces" for our template types
struct Motor {
Motor(int directionPin, int speedPin);
bool start();
bool stop();
};
class Car {
Motor mMotor{5 /* directionPin */, 10 /* speedPin */};
public:
void drive();
};
If Car was to become a template with Motor as a template type
would we ensure that Motor has start and stop functions?
platis.solutions
©
for
GRCPP
Meetup
Template "interfaces" without concepts: SFINAE
template<typename T, typename = void>
struct IsMotor : std::false_type {};
template<typename T>
struct IsMotor<T, std::void_t<decltype(std::declval<T>().start()),
decltype(std::declval<T>().stop())>>
: std::true_type {};
template<typename Motor>
class Car {
static_assert(IsMotor<Motor>::value, "Motor needs start and stop");
Motor mMotor;
public:
void drive();
};
platis.solutions
©
for
GRCPP
Meetup
Template "interfaces" with concepts
template<typename T>
concept Motor = requires(T m) {
T{int{}, int{}}; // Constructible with two ints
m.start(); // T has a public start method
m.stop(); // T has a public stop method
};
template<Motor M>
class Car {
M mMotor{5 /* directionPin */, 10 /* speedPin */};
public:
void drive();
};
Much simpler? Let's look at the requires expression.
platis.solutions
©
for
GRCPP
Meetup
requires as a "contract"
template <typename T>
concept Motor = requires(T m) {
m.start();
m.stop();
};
Evaluates to true if the expression is valid after substitution
false otherwise but no error is generated if ill-formed
Every line is a new "term" in the "contract", all must be satisfied
Do not see them as "commands" but as "terms in a contract"
Full syntax: cppreference.com/w/cpp/language/requires
platis.solutions
©
for
GRCPP
Meetup
requires requiring...
template <typename T>
concept Gyroscope = requires(T g, std::vector<int> params, int frequency) { // 1
T{params}; // 2
g.calibrate(); // 3
{ g.getAngle() } -> std::same_as<double>; // 4
g.setFrequency(frequency); // 5
};
1. "Objects" needed to express the requirements/statements
2. A constructor accepting a std::vector<int>
3. A calibrate() member function existing (return type unchecked)
4. getAngle() member function returning double
5. setFrequency(int) member function accepting an int
platis.solutions
©
for
GRCPP
Meetup
Verify getAngle that returns double exists with SFINAE:
template<typename T, typename = void>
struct HasGetAngle : std::false_type {};
template<typename T>
struct HasGetAngle<
T, std::enable_if_t<std::is_same<
double, decltype(std::declval<T>().getAngle())>::value>>
: std::true_type {};
// Alternatively:
// template<typename T>
// struct HasGetAngle<T, std::void_t<decltype(std::declval<T>().getAngle())>>
// : std::bool_constant<std::is_same<
// double, decltype(std::declval<T>().getAngle())>::value> {};
This is a lot of boilerplate code for a "simple" check.
platis.solutions
©
for
GRCPP
Meetup
More requires
template<typename T>
concept MyBigConcept = requires(T a, T b, std::ostream& out) {
a + b; // Addable with its own type
a++; // Incrementable
{ a == b } -> std::same_as<bool>; // Equality comparable
typename T::inner; // T::inner is a type (exists)
{ out << a } -> std::same_as<std::ostream&>; // Streamable to std::ostream
requires std::integral<typename T::value_type>; // T::value_type satisfies std::integral
{ a.size() } -> std::integral; // Return type satisfies other concept
{ T::Instances } -> std::same_as<std::size_t>; // T::Instances static and std::size_t
a.id; // `id` is a public member variable
};
platis.solutions
©
for
GRCPP
Meetup
Choosing the right candidate
template<typename Robot>
void handleEnemies(Robot) { std::cout << "I surrender!n"; }
template<typename Robot>
requires HasBullets<Robot>
void handleEnemies(Robot r) { r.shootBullets(); }
template<HasMissiles Robot>
void handleEnemies(Robot r) { r.shootMissiles(); }
struct RobotA { void shootBullets() { std::cout << "Bang!n"; } };
struct RobotB { void shootMissiles() { std::cout << "Shooosh!n"; } };
struct RobotC {};
handleEnemies(RobotA{}); // "Bang!"
handleEnemies(RobotB{}); // "Shooosh!"
handleEnemies(RobotC{}); // "I surrender!"
platis.solutions
©
for
GRCPP
Meetup
Specializing member functions
template<typename Motor>
concept HasOdometer = requires(Motor m) {
m.getPulses();
};
template<typename Motor>
struct Car {
void drive() { std::cout << "Driven"; }
void drive() requires HasOdometer<Motor> {
std::cout << "Drive with cruise controln";
}
};
The compiler chooses the most specialized member function.
The trailing requires clause becomes very useful here.
platis.solutions
©
for
GRCPP
Meetup
if constexpr and requires
template<typename T>
void print_info(T value) {
if constexpr (requires(int i) { value.foo(i); }) {
std::cout << "T has foo(int) member functionn";
} else if constexpr (requires { value.bar(); }) {
std::cout << "T has bar() member functionn";
} else {
std::cout << "T has neither foo(int) nor bar() member functionsn";
}
}
Create concepts on the fly with if constexpr and requires .
We may specify arguments in the requires clause.
platis.solutions
©
for
GRCPP
Meetup
What will be printed out?
template<typename T>
constexpr void print_type_info(const T& value) {
if constexpr (requires { std::is_integral_v<T>; }) {
std::cout << "Value is integral: " << value << std::endl;
} else {
std::cout << "Value is not integral" << std::endl;
}
}
print_type_info(5);
print_type_info(3.14);
print_type_info("Hello");
"Value is integral..." 3 times. Why?
Curly-braced requires becomes true if statements are valid
platis.solutions
©
for
GRCPP
Meetup
(Avoid) Concepts that are always satisfied
template<typename T>
concept AlwaysSatisfied1 = true;
template<typename T>
concept AlwaysSatisfied2 = requires { false; };
template<typename T>
concept AlwaysSatisfied3 = requires(T t) {
std::is_integral_v<T>;
std::is_floating_point_v<T>;
};
static_assert(AlwaysSatisfied1<int>); // Hardcoded to true
static_assert(AlwaysSatisfied2<int>); // `false;` is a valid statement
static_assert(AlwaysSatisfied3<int>); // `true;` and `false;` are valid
platis.solutions
©
for
GRCPP
Meetup
Which of the following constraints are always satisfied?
template<typename T>
concept Integral = requires {
std::integral<T>; // 1
requires std::integral<T>; // 2
std::is_integral_v<T>; // 3
{ T{} } -> std::integral; // 4
};
std::integral<T> always a valid expression ( true or false )
requires std::integral<T> becomes invalid if T is not integral
std::is_integral_v<T> always a valid expression ( true or false )
{ T{} } -> std::integral becomes invalid if T is not integral
platis.solutions
©
for
GRCPP
Meetup
requires { requires <true|false> }
template<typename T>
constexpr void print_type_info(const T& value) {
if constexpr (requires { requires std::is_integral_v<T>; }) {
std::cout << "Value is integral: " << value << std::endl;
} else {
std::cout << "Value is not integral" << std::endl;
}
}
requires without curly braces becomes valid if expression is true
requires std::is_integral_v<T>; is ill-formed if T not integral
requires with curly braces evaluates to true for valid statements
requires { ... }; is false if nested requires is ill-formed
platis.solutions
©
for
GRCPP
Meetup
requires requires { statements...; }
template<typename Container>
requires requires(Container a, Container::value_type v1, Container::value_type v2) {
{ a.begin() } -> std::input_iterator;
{ a.end() } -> std::sentinel_for<decltype(a.begin())>;
{ a.size() } -> std::same_as<std::size_t>;
{ v1 < v2 } -> std::same_as<bool>;
}
void print_sorted(Container& c) { /* ... */ }
requires with curly braces checks if the statements are valid
Becomes true if all statements are valid, false otherwise
requires without curly braces checks if the expression is true
Becomes valid if the expression is true , ill-formed otherwise
platis.solutions
©
for
GRCPP
Meetup
Concepts with multiple types
template<typename Motor, typename Odometer>
concept CompatibleOdometry = requires(Motor m, Odometer o) {
m.attach(o);
};
template<typename Motor, typename Odometer>
requires CompatibleOdometry<Motor, Odometer>
class Smartcar {
public:
Smartcar(Motor left, Motor right, Odometer odometer) { /* ... */ }
};
CompatibleOdometry requires Motor and Odometer to be compatible
Motor with member function attach accepting Odometer
platis.solutions
©
for
GRCPP
Meetup
Concepts with lambdas (no template parameter
list)
template<typename Car>
concept CanStop = requires(Car car) { car.stop(); };
template<typename Sensor>
concept CanDetectObstruction = requires(Sensor sensor) { sensor.isObstructed(); };
auto stopIfObstructed = [](auto& car, auto& sensor) -> void
requires CanDetectObstruction<decltype(sensor)> && CanStop<decltype(car)>
{
if (sensor.isObstructed()) { car.stop(); }
};
requires goes after the (optional) trailing return type
platis.solutions
©
for
GRCPP
Meetup
Concepts with lambdas (no template parameter
list)
auto stopIfObstructed2 = [](auto& car, auto& sensor)
requires requires {
{ sensor.isObstructed() } -> std::convertible_to<bool>;
car.stop();
}
{
if (sensor.isObstructed()) { car.stop(); }
};
Requirements on the fly with requires requires { statements...; }
platis.solutions
©
for
GRCPP
Meetup
Concepts with lambdas (template parameter list)
auto stop1 = []<typename Car, typename Sensor>
requires CanDetectObstruction<Sensor> && CanStop<Car>
(Car & car, Sensor & sensor) {
if (sensor.isObstructed()) { car.stop(); }
};
auto stop2 = []<CanStop Car, CanDetectObstruction Sensor>(Car& c, Sensor& s) {
if (s.isObstructed()) { c.stop(); }
};
requires after the lambda template parameter list
Can also go after the (optional) trailing return type
Concepts as non-type template parameters
platis.solutions
©
for
GRCPP
Meetup
Takeaways
Concepts are named sets of requirements on template types
concept is to requires what a function is to a statement
Two types of requires which can be confusing:
requires without curly braces
Expects a boolean expression, evaluates to valid or ill-formed
requires with curly braces
Expects a block of statements, evaluates to true or false
platis.solutions
©
for
GRCPP
Meetup
Takeaways
Concepts simplify code and provide better error messages
Use concepts to create "interfaces" for template classes & methods
Skip reading code or compiler errors to find the right type to use
Avoid cryptic and verbose SFINAE constructs
static_assert is still useful for providing custom error messages
platis.solutions
©
for
GRCPP
Meetup

More Related Content

Similar to [GRCPP] Introduction to concepts (C++20)

Csharp4 delegates lambda_and_events
Csharp4 delegates lambda_and_eventsCsharp4 delegates lambda_and_events
Csharp4 delegates lambda_and_events
Abed Bukhari
 
An Introduction To C++Templates
An Introduction To C++TemplatesAn Introduction To C++Templates
An Introduction To C++Templates
Ganesh Samarthyam
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
ShriKant Vashishtha
 

Similar to [GRCPP] Introduction to concepts (C++20) (20)

Cpp17 and Beyond
Cpp17 and BeyondCpp17 and Beyond
Cpp17 and Beyond
 
C++ Template
C++ TemplateC++ Template
C++ Template
 
Beyond C++17
Beyond C++17Beyond C++17
Beyond C++17
 
Advanced Programming C++
Advanced Programming C++Advanced Programming C++
Advanced Programming C++
 
.NET 2015: Будущее рядом
.NET 2015: Будущее рядом.NET 2015: Будущее рядом
.NET 2015: Будущее рядом
 
Chapter 2
Chapter 2Chapter 2
Chapter 2
 
Legacy is Good
Legacy is GoodLegacy is Good
Legacy is Good
 
Bw14
Bw14Bw14
Bw14
 
Java Generics
Java GenericsJava Generics
Java Generics
 
Csharp4 delegates lambda_and_events
Csharp4 delegates lambda_and_eventsCsharp4 delegates lambda_and_events
Csharp4 delegates lambda_and_events
 
An Introduction To C++Templates
An Introduction To C++TemplatesAn Introduction To C++Templates
An Introduction To C++Templates
 
Practices For Becoming A Better Programmer
Practices For Becoming A Better ProgrammerPractices For Becoming A Better Programmer
Practices For Becoming A Better Programmer
 
Generic programming and concepts that should be in C++
Generic programming and concepts that should be in C++Generic programming and concepts that should be in C++
Generic programming and concepts that should be in C++
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
 
Savitch Ch 17
Savitch Ch 17Savitch Ch 17
Savitch Ch 17
 
The Goal and The Journey - Turning back on one year of C++14 Migration
The Goal and The Journey - Turning back on one year of C++14 MigrationThe Goal and The Journey - Turning back on one year of C++14 Migration
The Goal and The Journey - Turning back on one year of C++14 Migration
 
[OLD VERSION, SEE DESCRIPTION FOR THE NEWER VERSION LINK] Hot С++: Universal ...
[OLD VERSION, SEE DESCRIPTION FOR THE NEWER VERSION LINK] Hot С++: Universal ...[OLD VERSION, SEE DESCRIPTION FOR THE NEWER VERSION LINK] Hot С++: Universal ...
[OLD VERSION, SEE DESCRIPTION FOR THE NEWER VERSION LINK] Hot С++: Universal ...
 
Metaprogramming
MetaprogrammingMetaprogramming
Metaprogramming
 
Savitch ch 17
Savitch ch 17Savitch ch 17
Savitch ch 17
 
OOP - Templates
OOP - TemplatesOOP - Templates
OOP - Templates
 

More from Dimitrios Platis

Introduction to CMake
Introduction to CMakeIntroduction to CMake
Introduction to CMake
Dimitrios Platis
 
How to create your own Linux distribution (embedded-gothenburg)
How to create your own Linux distribution (embedded-gothenburg)How to create your own Linux distribution (embedded-gothenburg)
How to create your own Linux distribution (embedded-gothenburg)
Dimitrios Platis
 

More from Dimitrios Platis (11)

OpenAI API crash course
OpenAI API crash courseOpenAI API crash course
OpenAI API crash course
 
Builder pattern in C++.pdf
Builder pattern in C++.pdfBuilder pattern in C++.pdf
Builder pattern in C++.pdf
 
Interprocess communication with C++.pdf
Interprocess communication with C++.pdfInterprocess communication with C++.pdf
Interprocess communication with C++.pdf
 
Lambda expressions in C++
Lambda expressions in C++Lambda expressions in C++
Lambda expressions in C++
 
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Writing SOLID C++ [gbgcpp meetup @ Zenseact]Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
 
Introduction to CMake
Introduction to CMakeIntroduction to CMake
Introduction to CMake
 
Pointer to implementation idiom
Pointer to implementation idiomPointer to implementation idiom
Pointer to implementation idiom
 
Afry software safety ISO26262 (Embedded @ Gothenburg Meetup)
Afry software safety ISO26262 (Embedded @ Gothenburg Meetup)Afry software safety ISO26262 (Embedded @ Gothenburg Meetup)
Afry software safety ISO26262 (Embedded @ Gothenburg Meetup)
 
How to create your own Linux distribution (embedded-gothenburg)
How to create your own Linux distribution (embedded-gothenburg)How to create your own Linux distribution (embedded-gothenburg)
How to create your own Linux distribution (embedded-gothenburg)
 
[grcpp] Refactoring for testability c++
[grcpp] Refactoring for testability c++[grcpp] Refactoring for testability c++
[grcpp] Refactoring for testability c++
 
Refactoring for testability c++
Refactoring for testability c++Refactoring for testability c++
Refactoring for testability c++
 

Recently uploaded

Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Lisi Hocke
 

Recently uploaded (20)

Modern binary build systems - PyCon 2024
Modern binary build systems - PyCon 2024Modern binary build systems - PyCon 2024
Modern binary build systems - PyCon 2024
 
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdfThe Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
 
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
 
architecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdfarchitecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdf
 
BusinessGPT - Security and Governance for Generative AI
BusinessGPT  - Security and Governance for Generative AIBusinessGPT  - Security and Governance for Generative AI
BusinessGPT - Security and Governance for Generative AI
 
Abortion Clinic In Springs ](+27832195400*)[ 🏥 Safe Abortion Pills in Springs...
Abortion Clinic In Springs ](+27832195400*)[ 🏥 Safe Abortion Pills in Springs...Abortion Clinic In Springs ](+27832195400*)[ 🏥 Safe Abortion Pills in Springs...
Abortion Clinic In Springs ](+27832195400*)[ 🏥 Safe Abortion Pills in Springs...
 
how-to-download-files-safely-from-the-internet.pdf
how-to-download-files-safely-from-the-internet.pdfhow-to-download-files-safely-from-the-internet.pdf
how-to-download-files-safely-from-the-internet.pdf
 
Abortion Clinic Pretoria ](+27832195400*)[ Abortion Clinic Near Me ● Abortion...
Abortion Clinic Pretoria ](+27832195400*)[ Abortion Clinic Near Me ● Abortion...Abortion Clinic Pretoria ](+27832195400*)[ Abortion Clinic Near Me ● Abortion...
Abortion Clinic Pretoria ](+27832195400*)[ Abortion Clinic Near Me ● Abortion...
 
A Deep Dive into Secure Product Development Frameworks.pdf
A Deep Dive into Secure Product Development Frameworks.pdfA Deep Dive into Secure Product Development Frameworks.pdf
A Deep Dive into Secure Product Development Frameworks.pdf
 
^Clinic ^%[+27788225528*Abortion Pills For Sale In birch acres
^Clinic ^%[+27788225528*Abortion Pills For Sale In birch acres^Clinic ^%[+27788225528*Abortion Pills For Sale In birch acres
^Clinic ^%[+27788225528*Abortion Pills For Sale In birch acres
 
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
 
Effective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeConEffective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeCon
 
OpenChain Webinar: AboutCode and Beyond - End-to-End SCA
OpenChain Webinar: AboutCode and Beyond - End-to-End SCAOpenChain Webinar: AboutCode and Beyond - End-to-End SCA
OpenChain Webinar: AboutCode and Beyond - End-to-End SCA
 
Weeding your micro service landscape.pdf
Weeding your micro service landscape.pdfWeeding your micro service landscape.pdf
Weeding your micro service landscape.pdf
 
Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024
 
Abortion Clinic In Polokwane ](+27832195400*)[ 🏥 Safe Abortion Pills in Polok...
Abortion Clinic In Polokwane ](+27832195400*)[ 🏥 Safe Abortion Pills in Polok...Abortion Clinic In Polokwane ](+27832195400*)[ 🏥 Safe Abortion Pills in Polok...
Abortion Clinic In Polokwane ](+27832195400*)[ 🏥 Safe Abortion Pills in Polok...
 
^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto
^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto
^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto
 
Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...
Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...
Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...
 
StrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzi.pdf
StrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzi.pdfStrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzi.pdf
StrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzi.pdf
 
The Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationThe Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test Automation
 

[GRCPP] Introduction to concepts (C++20)

  • 1. concepts (since C++20) Create "interfaces" for your templates platis.solutions © for GRCPP Meetup
  • 2. About me Grew up in Rodos, Greece Software Engineering at GU & Chalmers Working with embedded systems Teaching DIT113, DAT265, Thesis supervision C++, Coursera, Udemy Open source projects https://platis.solutions https://github.com/platisd Email: dimitris@platis.solutions platis.solutions © for GRCPP Meetup
  • 3. Requirements on template arguments C++20 introduces constraints Specify requirements on template arguments Seamless selection of the appropriate overload or specialization Named sets of such requirements are called concepts Constraints and concepts requires expression platis.solutions © for GRCPP Meetup
  • 4. Why do we need concepts? template <typename Camera> class AutonomousCar { Camera mCamera; public: // ... A lot of code }; How do we ensure that Camera has all required functions? Normally, we read the Camera interface, with templates we can't. platis.solutions © for GRCPP Meetup
  • 5. Why do we need concepts? template <typename T> T getMedianNumber(std::vector<T> values) { std::sort(values.begin(), values.end()); return values[values.size() / 2]; } What kind of types does it make sense for getMedian accept? What kind of types can getMedian accept? platis.solutions © for GRCPP Meetup
  • 6. Let's make our getMedianNumber more explicit template<typename T> T getMedianNumber(std::vector<T> values) { static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>, "T must be an integral or floating-point"); std::sort(values.begin(), values.end()); return values[values.size() / 2]; } static_assert is great, but can make things more readable? platis.solutions © for GRCPP Meetup
  • 7. Let's make our own constraint template<typename T> requires std::integral<T> || std::floating_point<T> // requires std::is_integral_v<T> || std::is_floating_point_v<T> T getMedianNumber(std::vector<T> values) { std::sort(values.begin(), values.end()); return values[values.size() / 2]; } std::vector<std::string> files{"file22.txt", "file11.txt", "file33.txt"}; std::cout << getMedianNumber(files) << std::endl; // Compilation error std::vector numbers{0, 9, 5, 7, 3, 6, 2, 8, 1, 4, 10}; std::cout << getMedianNumber(numbers) << std::endl; // 5 "No operand of the disjunction is satisfied" requires std::integral<T> || std::floating_point<T> platis.solutions © for GRCPP Meetup
  • 8. Let's make our own concept template<typename T> concept Number = std::integral<T> || std::floating_point<T>; // concept Number = std::is_integral_v<T> || std::is_floating_point_v<T>; template<typename T> requires Number<T> T getMedianNumber(std::vector<T> values) { std::sort(values.begin(), values.end()); return values[values.size() / 2]; } Something that is satisfied or not, often treated "like a boolean" Use concepts in requires clauses or to compose other concepts std::integral and std::floating_point are built-in concepts platis.solutions © for GRCPP Meetup
  • 9. Trailing requires syntax is also possible: template<typename T> T getMedianNumber(std::vector<T> values) requires Number<T> // <--- Trailing requires { std::sort(values.begin(), values.end()); return values[values.size() / 2]; } It is exactly the same as the previous example: template<typename T> requires Number<T> // <--- Leading requires T getMedianNumber(std::vector<T> values) { std::sort(values.begin(), values.end()); return values[values.size() / 2]; } platis.solutions © for GRCPP Meetup
  • 10. Let's make getMedianNumber more readable template<typename T> concept Number = std::integral<T> || std::floating_point<T>; template<Number T> T getMedianNumber(std::vector<T> values) { std::sort(values.begin(), values.end()); return values[values.size() / 2]; } Use a concept as a non-type template parameter Highly expressive and readable Constrain the template parameter is a way that feels intuitive platis.solutions © for GRCPP Meetup
  • 11. concept vs requires template<Number T> requires std::is_integral_v<T> || std::is_floating_point_v<T> T getMedianNumber1(std::vector<T> values) { /* ... */ } template<typename T> requires Number<T> T getMedianNumber2(std::vector<T> values) { /* ... */ } requires used to express requirements on template arguments A concept is a named set of requirements A concept is to a requires what a function is to a statement In getMedianNumber2 we named the requirements Number platis.solutions © for GRCPP Meetup
  • 12. How do you requires ? template<typename T> concept Motor = requires(T m) { // <--- `requires` with curly braces m.start(); m.stop(); }; template<typename T> requires Motor<T> // <--- `requires` without curly braces class Car { // ... }; Without curly braces: requires <some boolean expression> With curly braces: requires(T m) { statements...; } platis.solutions © for GRCPP Meetup
  • 13. requires without curly braces template<typename T> requires std::is_constructible_v<T, std::string, int> void createWithStringAndInt() { /* ... */ } Expects a boolean expression to follow If the boolean expression is true , requires is satisfied and valid If the expression is false , requires is ill-formed No error is generated if requires is ill-formed May also be with parentheses: requires ( ... ) platis.solutions © for GRCPP Meetup
  • 14. requires with curly braces template<typename T> concept StringAndIntConstructible = requires(std::string s, int i) { T{s, i}; }; Expects a block of statements to follow {within curly braces} Optionally preceded by objects for statement formulation After type substitution if statements valid, requires is true If any statement is ill-formed, requires evaluates to false No error is generated if any statement is ill-formed platis.solutions © for GRCPP Meetup
  • 15. "Interfaces" for our template types struct Motor { Motor(int directionPin, int speedPin); bool start(); bool stop(); }; class Car { Motor mMotor{5 /* directionPin */, 10 /* speedPin */}; public: void drive(); }; If Car was to become a template with Motor as a template type would we ensure that Motor has start and stop functions? platis.solutions © for GRCPP Meetup
  • 16. Template "interfaces" without concepts: SFINAE template<typename T, typename = void> struct IsMotor : std::false_type {}; template<typename T> struct IsMotor<T, std::void_t<decltype(std::declval<T>().start()), decltype(std::declval<T>().stop())>> : std::true_type {}; template<typename Motor> class Car { static_assert(IsMotor<Motor>::value, "Motor needs start and stop"); Motor mMotor; public: void drive(); }; platis.solutions © for GRCPP Meetup
  • 17. Template "interfaces" with concepts template<typename T> concept Motor = requires(T m) { T{int{}, int{}}; // Constructible with two ints m.start(); // T has a public start method m.stop(); // T has a public stop method }; template<Motor M> class Car { M mMotor{5 /* directionPin */, 10 /* speedPin */}; public: void drive(); }; Much simpler? Let's look at the requires expression. platis.solutions © for GRCPP Meetup
  • 18. requires as a "contract" template <typename T> concept Motor = requires(T m) { m.start(); m.stop(); }; Evaluates to true if the expression is valid after substitution false otherwise but no error is generated if ill-formed Every line is a new "term" in the "contract", all must be satisfied Do not see them as "commands" but as "terms in a contract" Full syntax: cppreference.com/w/cpp/language/requires platis.solutions © for GRCPP Meetup
  • 19. requires requiring... template <typename T> concept Gyroscope = requires(T g, std::vector<int> params, int frequency) { // 1 T{params}; // 2 g.calibrate(); // 3 { g.getAngle() } -> std::same_as<double>; // 4 g.setFrequency(frequency); // 5 }; 1. "Objects" needed to express the requirements/statements 2. A constructor accepting a std::vector<int> 3. A calibrate() member function existing (return type unchecked) 4. getAngle() member function returning double 5. setFrequency(int) member function accepting an int platis.solutions © for GRCPP Meetup
  • 20. Verify getAngle that returns double exists with SFINAE: template<typename T, typename = void> struct HasGetAngle : std::false_type {}; template<typename T> struct HasGetAngle< T, std::enable_if_t<std::is_same< double, decltype(std::declval<T>().getAngle())>::value>> : std::true_type {}; // Alternatively: // template<typename T> // struct HasGetAngle<T, std::void_t<decltype(std::declval<T>().getAngle())>> // : std::bool_constant<std::is_same< // double, decltype(std::declval<T>().getAngle())>::value> {}; This is a lot of boilerplate code for a "simple" check. platis.solutions © for GRCPP Meetup
  • 21. More requires template<typename T> concept MyBigConcept = requires(T a, T b, std::ostream& out) { a + b; // Addable with its own type a++; // Incrementable { a == b } -> std::same_as<bool>; // Equality comparable typename T::inner; // T::inner is a type (exists) { out << a } -> std::same_as<std::ostream&>; // Streamable to std::ostream requires std::integral<typename T::value_type>; // T::value_type satisfies std::integral { a.size() } -> std::integral; // Return type satisfies other concept { T::Instances } -> std::same_as<std::size_t>; // T::Instances static and std::size_t a.id; // `id` is a public member variable }; platis.solutions © for GRCPP Meetup
  • 22. Choosing the right candidate template<typename Robot> void handleEnemies(Robot) { std::cout << "I surrender!n"; } template<typename Robot> requires HasBullets<Robot> void handleEnemies(Robot r) { r.shootBullets(); } template<HasMissiles Robot> void handleEnemies(Robot r) { r.shootMissiles(); } struct RobotA { void shootBullets() { std::cout << "Bang!n"; } }; struct RobotB { void shootMissiles() { std::cout << "Shooosh!n"; } }; struct RobotC {}; handleEnemies(RobotA{}); // "Bang!" handleEnemies(RobotB{}); // "Shooosh!" handleEnemies(RobotC{}); // "I surrender!" platis.solutions © for GRCPP Meetup
  • 23. Specializing member functions template<typename Motor> concept HasOdometer = requires(Motor m) { m.getPulses(); }; template<typename Motor> struct Car { void drive() { std::cout << "Driven"; } void drive() requires HasOdometer<Motor> { std::cout << "Drive with cruise controln"; } }; The compiler chooses the most specialized member function. The trailing requires clause becomes very useful here. platis.solutions © for GRCPP Meetup
  • 24. if constexpr and requires template<typename T> void print_info(T value) { if constexpr (requires(int i) { value.foo(i); }) { std::cout << "T has foo(int) member functionn"; } else if constexpr (requires { value.bar(); }) { std::cout << "T has bar() member functionn"; } else { std::cout << "T has neither foo(int) nor bar() member functionsn"; } } Create concepts on the fly with if constexpr and requires . We may specify arguments in the requires clause. platis.solutions © for GRCPP Meetup
  • 25. What will be printed out? template<typename T> constexpr void print_type_info(const T& value) { if constexpr (requires { std::is_integral_v<T>; }) { std::cout << "Value is integral: " << value << std::endl; } else { std::cout << "Value is not integral" << std::endl; } } print_type_info(5); print_type_info(3.14); print_type_info("Hello"); "Value is integral..." 3 times. Why? Curly-braced requires becomes true if statements are valid platis.solutions © for GRCPP Meetup
  • 26. (Avoid) Concepts that are always satisfied template<typename T> concept AlwaysSatisfied1 = true; template<typename T> concept AlwaysSatisfied2 = requires { false; }; template<typename T> concept AlwaysSatisfied3 = requires(T t) { std::is_integral_v<T>; std::is_floating_point_v<T>; }; static_assert(AlwaysSatisfied1<int>); // Hardcoded to true static_assert(AlwaysSatisfied2<int>); // `false;` is a valid statement static_assert(AlwaysSatisfied3<int>); // `true;` and `false;` are valid platis.solutions © for GRCPP Meetup
  • 27. Which of the following constraints are always satisfied? template<typename T> concept Integral = requires { std::integral<T>; // 1 requires std::integral<T>; // 2 std::is_integral_v<T>; // 3 { T{} } -> std::integral; // 4 }; std::integral<T> always a valid expression ( true or false ) requires std::integral<T> becomes invalid if T is not integral std::is_integral_v<T> always a valid expression ( true or false ) { T{} } -> std::integral becomes invalid if T is not integral platis.solutions © for GRCPP Meetup
  • 28. requires { requires <true|false> } template<typename T> constexpr void print_type_info(const T& value) { if constexpr (requires { requires std::is_integral_v<T>; }) { std::cout << "Value is integral: " << value << std::endl; } else { std::cout << "Value is not integral" << std::endl; } } requires without curly braces becomes valid if expression is true requires std::is_integral_v<T>; is ill-formed if T not integral requires with curly braces evaluates to true for valid statements requires { ... }; is false if nested requires is ill-formed platis.solutions © for GRCPP Meetup
  • 29. requires requires { statements...; } template<typename Container> requires requires(Container a, Container::value_type v1, Container::value_type v2) { { a.begin() } -> std::input_iterator; { a.end() } -> std::sentinel_for<decltype(a.begin())>; { a.size() } -> std::same_as<std::size_t>; { v1 < v2 } -> std::same_as<bool>; } void print_sorted(Container& c) { /* ... */ } requires with curly braces checks if the statements are valid Becomes true if all statements are valid, false otherwise requires without curly braces checks if the expression is true Becomes valid if the expression is true , ill-formed otherwise platis.solutions © for GRCPP Meetup
  • 30. Concepts with multiple types template<typename Motor, typename Odometer> concept CompatibleOdometry = requires(Motor m, Odometer o) { m.attach(o); }; template<typename Motor, typename Odometer> requires CompatibleOdometry<Motor, Odometer> class Smartcar { public: Smartcar(Motor left, Motor right, Odometer odometer) { /* ... */ } }; CompatibleOdometry requires Motor and Odometer to be compatible Motor with member function attach accepting Odometer platis.solutions © for GRCPP Meetup
  • 31. Concepts with lambdas (no template parameter list) template<typename Car> concept CanStop = requires(Car car) { car.stop(); }; template<typename Sensor> concept CanDetectObstruction = requires(Sensor sensor) { sensor.isObstructed(); }; auto stopIfObstructed = [](auto& car, auto& sensor) -> void requires CanDetectObstruction<decltype(sensor)> && CanStop<decltype(car)> { if (sensor.isObstructed()) { car.stop(); } }; requires goes after the (optional) trailing return type platis.solutions © for GRCPP Meetup
  • 32. Concepts with lambdas (no template parameter list) auto stopIfObstructed2 = [](auto& car, auto& sensor) requires requires { { sensor.isObstructed() } -> std::convertible_to<bool>; car.stop(); } { if (sensor.isObstructed()) { car.stop(); } }; Requirements on the fly with requires requires { statements...; } platis.solutions © for GRCPP Meetup
  • 33. Concepts with lambdas (template parameter list) auto stop1 = []<typename Car, typename Sensor> requires CanDetectObstruction<Sensor> && CanStop<Car> (Car & car, Sensor & sensor) { if (sensor.isObstructed()) { car.stop(); } }; auto stop2 = []<CanStop Car, CanDetectObstruction Sensor>(Car& c, Sensor& s) { if (s.isObstructed()) { c.stop(); } }; requires after the lambda template parameter list Can also go after the (optional) trailing return type Concepts as non-type template parameters platis.solutions © for GRCPP Meetup
  • 34. Takeaways Concepts are named sets of requirements on template types concept is to requires what a function is to a statement Two types of requires which can be confusing: requires without curly braces Expects a boolean expression, evaluates to valid or ill-formed requires with curly braces Expects a block of statements, evaluates to true or false platis.solutions © for GRCPP Meetup
  • 35. Takeaways Concepts simplify code and provide better error messages Use concepts to create "interfaces" for template classes & methods Skip reading code or compiler errors to find the right type to use Avoid cryptic and verbose SFINAE constructs static_assert is still useful for providing custom error messages platis.solutions © for GRCPP Meetup