Last active
January 9, 2026 23:46
-
-
Save TerensTare/09b4f3fde938fb3cab1628e8ab50a9cc to your computer and use it in GitHub Desktop.
Covariant function parameters in C++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Dart allows you to specify a "derived type" for parameters when you override virtual functions with the covariant keyword. | |
| // This tells the compiler you know for this class, the parameters will ever be of this type only. | |
| // (eg. when overriding operator==, you know you can only ever compare your type with objects of the same type). | |
| // The compiler needs to add runtime checks for every case this function is used, which is why by default C++ does not support this feature. | |
| // Nevertheless, we can extend our type hierarchy to support covariant parameters in a few steps, with an in-house error system that fits your coding style. | |
| // This example uses C++20, but if you replace concepts with the equivalent type traits it should work with earlier standards. | |
| #include <concepts> | |
| #include <fmt/core.h> | |
| // Step 1. Declare your base type. | |
| struct value | |
| { | |
| virtual ~value() {} | |
| // Step 2. Declare the function(s) you want to have covariant parameters. | |
| // In this case rhs will be covariant. | |
| virtual value const *add(value const *rhs) const = 0; | |
| // just for debugging | |
| virtual void print() const = 0; | |
| // helper to cast down the hierarchy | |
| template <typename T> | |
| inline T const *as() const noexcept | |
| { | |
| static_assert(std::derived_from<T, std::remove_cvref_t<decltype(*this)>>); | |
| return dynamic_cast<T const *>(this); | |
| } | |
| }; | |
| // this can be an exception/whatever | |
| struct bad_value final : value | |
| { | |
| // just propagate the error over the calls. | |
| inline value const *add(value const *) const { return this; } | |
| void print() const { fmt::println("bad value"); } | |
| }; | |
| // Step 3. Define helper class implementing the covariance bridge + checks. | |
| template <typename Derived> | |
| struct value_helper : value | |
| { | |
| // Step 4. Override the virtual function to call the derived's class function (if matching) or "fail". | |
| inline value const *add(value const *rhs) const final | |
| { | |
| if (auto r = rhs->as<Derived>()) | |
| return self().add(*r); | |
| else | |
| return new bad_value{}; | |
| } | |
| // CRTP helpers. | |
| inline auto &self() | |
| { | |
| static_assert(std::derived_from<Derived, value_helper<Derived>>); | |
| return *static_cast<Derived *>(this); | |
| } | |
| inline auto const &self() const | |
| { | |
| static_assert(std::derived_from<Derived, value_helper<Derived>>); | |
| return *static_cast<Derived const *>(this); | |
| } | |
| }; | |
| // sample type 1: an integer value | |
| struct int_value final : value_helper<int_value> | |
| { | |
| inline explicit int_value(int n) : n{n} {} | |
| // note this is not virtual | |
| // note rhs is a ref now because we know the pointer is valid | |
| inline int_value const *add(int_value const &rhs) const | |
| { | |
| return new int_value{n + rhs.n}; | |
| } | |
| void print() const { fmt::println("int({})", n); } | |
| int n; | |
| }; | |
| // sample type 2: a floating point value | |
| struct float_value final : value_helper<float_value> | |
| { | |
| inline explicit float_value(float f) : f{f} {} | |
| inline float_value const *add(float_value const &rhs) const | |
| { | |
| return new float_value{f + rhs.f}; | |
| } | |
| void print() const { fmt::println("float({})", f); } | |
| float f; | |
| }; | |
| int main() | |
| { | |
| value *v1 = new int_value{42}; | |
| value *v2 = new int_value{53}; | |
| value *v3 = new float_value{1.0f}; | |
| value *v4 = new float_value{2.0f}; | |
| v1->add(v2)->print(); // ok | |
| v3->add(v4)->print(); // ok | |
| v1->add(v3)->print(); // bad | |
| delete v4; | |
| delete v3; | |
| delete v2; | |
| delete v1; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment