---
date: 2025-12-17
tags: [cpp]
---
Concepts in C++
royyandzakiy.com/blog/2025-12/concepts/
Last post I shared about type traits to enable a SFINAE Class Template based Mocking to be used in the case of populating unit tests in cpp. Since C++20, the feature Concept is born. We will see how it is used in the code below, but my take on this is it’s essentially a more syntacticly nice Type Trait. But most importantly is, it has a better built in Error message (SFINAE errors are horrendeus imo)
In this example, I use a DigitalInput that will be used by a Button class (a study case of Embedded Systems). Here I assume the DigitalInput is some sort of given class from an library that we cannot modify, hence we would like to replace it with a Mock to enable non-HIL (Hardware in The Loop) tests. So I created a Concept DigitalInputLike that gets satisfied IFF any type has the init() and read() function in them. I also made an example of a BAD DigitalInput Mock that does not have the required functions, it will spit out a compilation error (nice catch concepts!)
During coding, the difference between using Type Traits compared to using Concepts is we do not need to have an addition of typename std::enable_if_t in the template typename call of the Button class. We also replace the use of typename with DigitalInputLike, and that automatically functions as a constraint.
#include <concepts>
#include <print>
// DigitalInputLike Concept
template <typename T>
concept DigitalInputLike = requires(T t) {
{ t.init() } -> std::same_as<void>;
{ t.read() } -> std::convertible_to<int>;
};
// Button Class Template (Uses Concept to Constrain it's instantiation)
template<DigitalInputLike T>
class Button {
public:
Button (T* input): m_DigitalInput(input) {}
void init() {
m_DigitalInput->init();
// rest of logic...
}
int read() {
return m_DigitalInput->read();
}
private:
T *m_DigitalInput;
};
// Digital Input (Real)
class DigitalInput {
public:
void init();
int read();
int process();
};
// Digital Input (Good Mock)
class DigitalInputMock {
public:
void init();
int read();
};
// Digital Input (Bad Mock)
class DigitalInputMock_Bad {
public:
/* void init(); */ // init() missing!
int not_read(); // read() missing!
};
auto main() -> int {
std::println("Real Digital Input, will proceed successfully");
DigitalInput digitalInput;
Button<DigitalInput> buttonWithReal(&digitalInput); // succeed
std::println("Good Mock, will proceed successfully");
DigitalInputMock digitalInputMock;
Button<DigitalInputMock> buttonWithMock(&digitalInputMock); // succeed
std::println("Bad Mock, will fail to compile if run");
DigitalInputMock_Bad digitalInputBad;
Button<DigitalInputMock_Bad> buttonWithBadMock(&digitalInputBad); // SFINAE in action! Compile error
return 0;
}
Output
(x86-64 clang 21.1.0 -std=c++23):
<source>:60:5: error: constraints not satisfied for class template 'Button' [with T = DigitalInputMock_Bad]
60 | Button<DigitalInputMock_Bad> buttonWithBadMock(&digitalInputBad); // SFINAE in action! Compile error
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:12:10: note: because 'DigitalInputMock_Bad' does not satisfy 'DigitalInputLike'
12 | template<DigitalInputLike T>
| ^
<source>:7:6: note: because 't.init()' would be invalid: no member named 'init' in 'DigitalInputMock_Bad'
7 | { t.init() } -> std::same_as<void>;
| ^
1 error generated.
Compiler returned: 1
If the buttonWithBadMock is commented out, it will churn out this
Real Digital Input, will proceed successfully
Good Mock, will proceed successfully
Bad Mock, will fail to compile if run