Step 5 of 9
Constraining templates with concepts
An unconstrained template accepts any type and fails deep inside its body with a wall of errors. A concept states the requirements up front, so a wrong type gets one clear error at the call site.
A concept can also require operations, not just categories like "integral". A requires expression lists expressions that must compile for the type, and optionally what they must return:
#include <concepts>
#include <iostream>
#include <string>
#include <vector>
template <typename T>
concept Priced = requires(const T& item) {
{ item.price() } -> std::convertible_to<double>;
{ item.label() } -> std::convertible_to<std::string>;
};
struct Book {
double price() const { return 12.5; }
std::string label() const { return "book"; }
};
struct Ticket {
int cents;
double price() const { return cents / 100.0; }
const char* label() const { return "ticket"; }
};
template <Priced P>
double total(const std::vector<P>& items) {
double sum = 0;
for (const auto& i : items) sum += i.price();
return sum;
}
int main() {
std::cout << total(std::vector<Book>(2)) << "\n";
std::cout << total(std::vector<Ticket>{{250}, {1000}}) << "\n";
std::cout << Priced<Book> << Priced<int> << "\n";
}
25
12.5
10
How it works
requires(const T& item) { ... }introduces a pretend variableitemand lists what must be valid for it.{ item.price() } -> std::convertible_to<double>;means "callingprice()compiles, and its result converts todouble".Ticket::label()returnsconst char*, which converts tostd::string, so it qualifies.Priced<int>is simplyfalse: a concept is a compile-time bool you can test directly.
Static polymorphism
Book and Ticket share no base class and have no virtual functions, yet total works for both. That's static polymorphism: the compiler generates a separate total for each type, with direct calls and zero run-time cost. Virtual functions (dynamic polymorphism) are still the tool when you need one container holding different types; concepts are for generic code where each call uses one type.
The concept for your task
template <typename T>
concept Shape = requires(const T& s) {
{ s.area() } -> std::convertible_to<double>;
{ s.name() } -> std::convertible_to<std::string>;
};
Your turn: write the Shape concept above and template <Shape S> double total_area(const std::vector<S>& shapes).
Previous: Writing your own trait Next: Value categories and reference collapsing