Using concepts and constraints with template classes split across files is a powerful way to improve code clarity and catch errors early. Let's explore how to implement this effectively.
First, let's define some concepts in a header file:
// Concepts.h
#pragma once
#include <concepts>
#include <iostream>
template <typename T>
concept Numeric = std::integral<T>
|| std::floating_point<T>;
template <typename T>
concept Printable = requires(T t) {
{ std::cout << t } -> std::same_as<std::ostream&>;
};
Now, let's declare our template class using these concepts:
// MyTemplate.h
#pragma once
#include "Concepts.h"
template <Numeric T, Printable U>
class MyTemplate {
public:
void foo();
void bar();
};
Implement the template methods in a separate .cpp file:
// MyTemplate.cpp
#include <iostream>
#include "MyTemplate.h"
template <Numeric T, Printable U>
void MyTemplate<T, U>::foo() {
std::cout << "foo() called\n";
}
template <Numeric T, Printable U>
void MyTemplate<T, U>::bar() {
std::cout << "bar() called\n";
}
// Explicit instantiations
template class MyTemplate<int, std::string>;
template class MyTemplate<double, char>;
Here's how you might use this template:
// main.cpp
#include <string>
#include "MyTemplate.h"
int main() {
MyTemplate<int, std::string> obj1;
obj1.foo(); // Logs "foo() called"
MyTemplate<double, char> obj2;
obj2.bar(); // Logs "bar() called"
// Compile-time error:
MyTemplate<std::string, int> obj3;
}
error: 'MyTemplate': the associated constraints are not satisfied
You can use concepts to require specific methods:
// Concepts.h
#include <concepts>
template <typename T>
concept Drawable = requires(T t) {
{ t.draw() } -> std::same_as<void>;
};
// MyTemplate.h
template <Drawable T>
class Renderer {
public:
void render(T& obj);
};
// MyTemplate.cpp
template <Drawable T>
void Renderer<T>::render(T& obj) {
obj.draw();
}
// Explicit instantiation for a Drawable type
class Circle {
public:
void draw() { /* ... */
}
};
template class Renderer<Circle>;
Remember, while concepts and constraints add compile-time checks, they don't affect runtime performance.
They're a tool for better design and earlier error detection. When using them with separate implementation files, make sure to explicitly instantiate all the template specializations you need.
Answers to questions are automatically generated and may not have been reviewed.
Learn how to separate class templates into declarations and definitions while avoiding common linker errors