C++20stladvanced
std::semiregular (C++20 Addition)
C++20 · The Big Four · Compiler flag: -std=c++20
Explanation
Concept requiring a type to be default-initializable, copyable, movable, assignable, and swappable -- everything a `regular` type needs except equality comparison. Models types that are "value-like" but lack `==`.
Real-World Scenario
Constrain a function object wrapper that stores callable objects which may not support equality comparison but must be copyable and default-constructible.
Quick Snippet
snippet.cpp
#include <concepts>
template<std::semiregular Func>
class CallbackHolder {
Func callback{};
public:
CallbackHolder() = default;
CallbackHolder(Func f) : callback(std::move(f)) {}
void invoke() { callback(); }
};Full Example
semiregular.cpp
// CodeNebula - C++20 Example: std::semiregular Concept
// Compile: g++ -std=c++20 -Wall -Wextra -o example semiregular.cpp
// std::semiregular<T> requires copyable and default_initializable.
// A semiregular type can be created, copied, moved, and assigned,
// but does not require equality comparison (unlike regular).
#include <concepts>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// A semiregular type (no operator== needed)
struct Transform {
double scale;
double offset;
Transform() : scale(1.0), offset(0.0) {}
Transform(double s, double o) : scale(s), offset(o) {}
double apply(double x) const { return x * scale + offset; }
};
// Not semiregular: no default constructor
struct MustInit {
int value;
MustInit(int v) : value(v) {}
};
// Not semiregular: not copyable
struct MoveOnly {
std::unique_ptr<int> ptr;
MoveOnly() : ptr(std::make_unique<int>(0)) {}
};
// Pipeline that stores semiregular function objects
template <std::semiregular T>
class Pipeline {
std::vector<T> stages_;
public:
void add(const T& stage) { stages_.push_back(stage); }
double run(double input) const {
double result = input;
for (const auto& stage : stages_)
result = stage.apply(result);
return result;
}
std::size_t size() const { return stages_.size(); }
};
template <typename T>
void check_semiregular(const char* name) {
std::cout << name << " - semiregular: " << std::semiregular<T>
<< ", copyable: " << std::copyable<T>
<< ", default_init: " << std::default_initializable<T> << "\n";
}
int main() {
// Build a transform pipeline
Pipeline<Transform> pipe;
pipe.add(Transform(2.0, 0.0)); // double the value
pipe.add(Transform(1.0, 10.0)); // add 10
pipe.add(Transform(0.5, 0.0)); // halve the value
std::cout << "Pipeline with " << pipe.size() << " stages:\n";
std::cout << " run(5) = " << pipe.run(5) << "\n";
std::cout << " run(10) = " << pipe.run(10) << "\n";
std::cout << " run(0) = " << pipe.run(0) << "\n";
// Default construction and copy
Transform t1(3.0, 1.0);
Transform t2 = t1; // copy
Transform t3; // default
std::cout << "\nt1.apply(5) = " << t1.apply(5) << "\n";
std::cout << "t2.apply(5) = " << t2.apply(5) << " (copy of t1)\n";
std::cout << "t3.apply(5) = " << t3.apply(5) << " (default)\n";
// Concept checks
std::cout << "\nSemiregular checks:\n";
check_semiregular<int>("int");
check_semiregular<Transform>("Transform");
check_semiregular<MustInit>("MustInit");
check_semiregular<MoveOnly>("MoveOnly");
return 0;
}
// Expected output:
// Pipeline with 3 stages:
// run(5) = 10
// run(10) = 15
// run(0) = 5
//
// t1.apply(5) = 16
// t2.apply(5) = 16 (copy of t1)
// t3.apply(5) = 5 (default)
//
// Semiregular checks:
// int - semiregular: 1, copyable: 1, default_init: 1
// Transform - semiregular: 1, copyable: 1, default_init: 1
// MustInit - semiregular: 0, copyable: 1, default_init: 0
// MoveOnly - semiregular: 0, copyable: 0, default_init: 1
Run command
g++ -std=c++20 -Wall -Wextra -o example semiregular.cpp