C++ Logo

std-proposals

Advanced search

[std-proposals] unified polymorphism by generic function with incomplete type

From: Kai Liao - AAVV <kai.liao-aavv_at_[hidden]>
Date: Wed, 22 Oct 2025 02:00:52 +0000
At current, c++ has so many different ways to do polymorphism (in my understanding, polymorphism is a way to write same code for different types). Too many ways means too complexed, so we hope we can do all kind of polymorphism in an unified way.
To save time, I will not discussion all the different ways of polymorphism, but focus on two most representative ways of polymorphism: virtual abstract class and template function.
Normally, if we can do polymorphism by virtual abstract class, we can do same thing by template function.
For example, we do polymorphism by virtual abstract class like below:
class rectangle_like {
public:
    virtual int getWidth() const = 0;
    virtual int getHigh() const = 0;
};

struct rectangle : public rectangle_like {
    int getWidth() const {
        return width;
    }
    int getHigh() const {
        return high;
    }

    int width;
    int high;
};

struct square : public rectangle_like {
    int getWidth() const {
        return side;
    }
    int getHigh() const {
        return side;
    }
    int side;
};

int area(const rectangle_like& t) {
    return t.getWidth() * t.getHigh();
}

int main() {
    rectangle r{};
    area(r);
    square s{};
    area(s);
}
Then similar code can be written by template function:
struct rectangle {
    int getWidth() const {
        return width;
    }
    int getHigh() const {
        return high;
    }

    int width;
    int high;
};

struct square {
    int getWidth() const {
        return side;
    }
    int getHigh() const {
        return side;
    }
    int side;
};

template <typename T>
int area(const T& t) {
    return t.getWidth() * t.getHigh();
}

int main() {
    rectangle r{};
    area(r);
    square s{};
    area(s);
}
As the example shown, we do polymorphism (write the area function) by both virtual abstract class and template function. In fact, ignore type erasure, if polymorphism can be done by virtual abstract class, the polymorphism can be done by template function, too. However, there are some polymorphism that can be done by template function, but cannot be done by virtual abstract class. For example, we can write selfCompare by template function:
template <typename T>
bool selfCompare(T t) {
    return t == t;
}

int main() {
    selfCompare(int{});
    selfCompare(double{});
}
But we cannot do same thing by abstract class directly. If we do that force, the code will be like:
class comparable {
public:
    virtual bool compare(const comparable&) const = 0;
};

class MyInt : public comparable {
    bool compare(const comparable& c) const {
        auto mi = dynamic_cast<const MyInt&>(c);
        assert(&mi != nullptr);
        return val < mi.val;
    }
    int val;
};

bool selfCompare(const comparable& c) {
    return c.compare(c);
}
We have to do dynamic_cast and assert even though we always compare two same objects with same type, which makes the code time consuming and hard to maintain. I have studied many c++ dynamic dispatch libs, all of them have same problem.
But we have an interesting tricky to solve the problem, looking at the template function:
template <typename T>
bool selfCompare(T t) {
    return t == t;
}
If we do some tiny changes on it: 1. Replace T to T&; 2. Pass compare function explicitly. Then the function will be like:
template <typename T>
bool selfCompare(const T& t, bool(*comparable)(const T&, const T&)) {
    return comparable(t, t);
}
After that, the template function works for incomplete type:
class incomplete;
auto selfCompareErase = selfCompare<incomplete>;
What’s more, incomplete type is always pointer/reference, pointer/reference to different type is always bit wise cast safety. So, every type can be converted to incomplete type, the conversion code will be like this:
template <typename T>
struct Comparable {
    const T& t;
    bool(*compare)(const T&, const T&);
};
template <typename T>
ErasedComparable erase_comparable(const Comparable<T>& comparable) {
    return std::bit_cast<ErasedComparable, Comparable<T>>(comparable);
}
So, we write a template function selfCompare, it works for static polymorphism, then we have a function selfCompareErase works for dynamic polymorphism automatically, that’s why I call it as unified polymorphism.
Here is an more complexed example, in the example, we can even sort an array: https://godbolt.org/z/TPdK683hY.
The unified polymorphism has these advantages:

  1. The type of interface is clear and simple, compare is just T->T->bool, no need to write nonsense T->comparable->bool.
  2. We can just store finite virtual functions like comparable, swapable and then combine them into infinity complex functions like sort.
  3. In most of cases, we don’t need RTTI. (We indeed need RTTI when we have independed multiple type erased objects, but just as the example shown, type erased objects are relating to each other in many cases.)
  4. We don’t have to store vptr for every objects, a group of objects can share a same vptr,
  5. If selfCompareErase can compile, all the selfCompare<T> can compile, the error report will be more clarify.
  6. Template function doesn’t have to be header file all the time, erased template function works for all type, too.
  7. Every template function can changed to pointer/reference version (ignore static), so every template function can be dynamic, we unify dynamic and static polymorphism.
Compare to other languages, all the function programming languages support such usage, what’s more, Swift can support such usage, too. (https://godbolt.org/z/4jWveKePx)(Rust cannot do that, for rust has object safety limitation.)
At current, it’s just an idea, not sure whether it will be a lib or language core. Please let me know if there are any good advice for that.

--- The information contained in this communication and any attachments is confidential and may be privileged, and is for the sole use of the intended recipient(s). Any unauthorized review, use, disclosure or distribution is prohibited. Unless explicitly stated otherwise in the body of this communication or the attachment thereto (if any), the information is provided on an AS-IS basis without any express or implied warranties or liabilities. To the extent you are relying on this information, you are doing so at your own risk. If you are not the intended recipient, please notify the sender immediately by replying to this message and destroy all copies of this message and any attachments. Neither the sender nor the company/group of companies he or she represents shall be liable for the proper and complete transmission of the information contained in this communication, or for any delay in its receipt.

Received on 2025-10-22 02:01:02