In applications such as ray tracing, an entity can be one of several types, which all share a common interface. For example, a Material
can either be a DiffuseMaterial
or a ReflectiveMaterial
, etc and they all support a method Color getColor(args);
In most applications, this issue is typically resolved using dynamic polymorphism:
class Material {
public:
virtual Color getColor() = 0;
// ...
}
class DiffuseMaterial: public Material {
public:
Color getColor() {
// ...
}
}
class ReflectiveMaterial: public Material {
public:
Color getColor() {
// ...
}
}
Then, a function can use Material*
to represent a material, which is either of these types.
This virtual class hierarchy not only allows the user to define a common behavior, it forces the user to define the virtual function, otherwise the programmer cannot create instances of the derived classes, because they are abstract. However, dynamic polymorphism causes a performance overhead due to the dynamic dispatch and lookup. This is a problem in high-performance programs.
More modern languages provide alternatives to dynamic polymorphism. Rust for example has trait
s (interfaces, which can be resolved at compile-time) and enum
s (the entities of which can be structs), which avoid dynamic polymorphism. For example,
enum Material {
DiffuseMaterial{member1, member2}, // a struct
ReflectiveMaterial {member1, member2, member3, } // a struct
}
How to achieve the same effect of dynamic polymorphism using compile-time polymorphism features in C++20? Namely, define a type, which can be one of several types. Forcing the programmer to define a custom implementation of the common behavior for each subtype would be nice, but isn't a must.
You could create a concept
that checks all the requirements that you have on a Material
:
template <class T>
concept IMaterial = requires(T m) {
// list the requirements here
{ m.getColor() } -> std::same_as<Color>;
};
The actual classes implementing the requirements can then be completely unrelated:
class DiffuseMaterial {
public:
Color getColor() { return Color{}; }
};
class ReflectiveMaterial {
public:
Color getColor() { return Color{}; }
};
... and you can use the concept
to only accept types that fulfills the requirements.
Example:
template<IMaterial M> // M must fulfill the requirements
class Foo : public M { // or composition if that makes more sense
void func() {
Color c = this->getColor();
}
};