0

I'm creating a library for embedded systems, because of that I can't use any RTTI features including dynamic_cast. To create a some thing like a dynamic_cast I've implemented the necessary components of RTTI myself according to this article. It might be that I did some things differently but the idea is the same.

Here's my implementation:

#include <iostream>

constexpr uint32_t primes[] = {
        2,
        3,
        5,
        7,
        11,
        13,
        17,
        19,
        23,
        29,
        31
};

#define DYNAMIC_POINTER_CAST_INIT(...)                                                           \
    virtual uint32_t dynamicPointerCastObjectId() override                                       \
    {                                                                                            \
        return DynamicPointerCastable<__VA_ARGS__>::dynamicPointerCastObjectId(); \
    }                                                                                            \
                                                                                                 \
    using DynamicPointerCastable<__VA_ARGS__>::dynamicPointerCastClassId;         \
    using DynamicPointerCastable<__VA_ARGS__>::dynamicPointerCastClassPrimeId

namespace
{
    uint32_t numberOfDynamicPointerCastables = 0;

    template <typename... Bs>
    struct genSubClassId
    {
        static uint32_t value;
    };

    template <typename B, typename... Bs>
    struct genSubClassId<B, Bs...>
    {
        static uint32_t value;
    };

    template <typename B>
    struct genSubClassId<B>
    {
        static uint32_t value;
    };
    template <>
    struct genSubClassId<>
    {
        static uint32_t value;
    };

    template <typename B, typename... Bs>
    uint32_t genSubClassId<B, Bs...>::value =
            B::dynamicPointerCastClassId() * genSubClassId<Bs...>::value;
    template <typename B>
    uint32_t genSubClassId<B>::value =
            B::dynamicPointerCastClassId();

    uint32_t genSubClassId<>::value = 1;
}

template <typename Sub, typename... Bases>
class DynamicPointerCastable
{

private:
    static uint32_t subPrimeId;

public:
    static uint32_t dynamicPointerCastClassPrimeId()
    {
        return subPrimeId;
    }

    static uint32_t dynamicPointerCastClassId()
    {
        return subPrimeId * genSubClassId<Bases...>::value;
    }

    virtual uint32_t dynamicPointerCastObjectId()
    {
        return DynamicPointerCastable<Sub, Bases...>::dynamicPointerCastClassId();
    }
};

template <typename T, typename Cast>
Cast *dynamicCastableCast(T *obj)
{
    if (obj->dynamicPointerCastObjectId() % Cast::dynamicPointerCastClassPrimeId() == 0)
        return (Cast *)obj;
    return nullptr;
}

template <typename Sub, typename... Bases>
uint32_t DynamicPointerCastable<Sub, Bases...>::subPrimeId = primes[numberOfDynamicPointerCastables++];

The basic idea is that I have a counter numberOfDynamicPointerCastables which is the index to a list of prime numbers primes (in the library this list contains 1000 prime numbers). Then the methods from DynamicPointerCastable are implemented in the sub classes with the CRTP. dynamicCastableCast then checks if the id of the class is divisible by the prime number associated with the class that you want to cast to.

Example:

struct Sub1 : public Base, public DynamicPointerCastable<Sub1, Base>
{
    DYNAMIC_POINTER_CAST_INIT(Sub1, Base);
    virtual std::string getName()
    {
        return "Sub1";
    }
};

struct SubGroup : public Base, public DynamicPointerCastable<SubGroup, Base>
{
    DYNAMIC_POINTER_CAST_INIT(SubGroup, Base);
    virtual std::string getName()
    {
        return "SubGroup";
    }
};

struct Sub2 : public SubGroup, public DynamicPointerCastable<Sub2, SubGroup>
{
    DYNAMIC_POINTER_CAST_INIT(Sub2, SubGroup);
    virtual std::string getName()
    {
        return "Sub2";
    }
};

int main()
{
    Base* sub1 = new Sub1;
    Base* sub2 = new Sub2;

    std::cout << "Expected Sub1: " << dynamicCastableCast<Base, Sub1>(sub1)->getName() << std::endl;
    std::cout << "Expected nullptr: " << dynamicCastableCast<Base, Sub2>(sub1) << std::endl;

    std::cout << "Expected Sub2: " << dynamicCastableCast<Base, SubGroup>(sub2)->getName() << std::endl;
    std::cout << "Expected Sub2: " << dynamicCastableCast<Base, Sub2>(sub2)->getName() << std::endl;
    std::cout << "Expected nullptr: " << dynamicCastableCast<Base, Sub1>(sub2) << std::endl;
}

Unfortunately tough I didn't manage to get the counter working at compile-time which means the whole array of prime numbers will be in the binary (I used gcc-11 with the flags -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -DNDEBUG).

Will the compiler even be able to just include the prime numbers in the binary that are necessary when the counter would be counting the classes at compile-time?

And when yes how could I make the counter work at compile-time?

Or is there an other solution to keep the binary as small as possible?

EDIT:

I've just implemented a meta function that can generate the n-th prime number up to n = 131 with max -ftemplate-depth=900 (default). 131 would probably be enough but I will optimize it to use less template depth. And I'm now going to measure the program size with the second method suggested by @Igor Tandetnik. The down size is that the prime<...>::atIndex() method takes FOREVER to compile.

Implementation:

#include <iostream>

template<size_t n, size_t i = 2>
constexpr typename std::enable_if<!(i * i <= n), bool>::type isPrime() noexcept
{
    return true;
}

template<size_t n, size_t i = 2>
constexpr typename std::enable_if<i * i <= n, bool>::type isPrime() noexcept
{
    return (n % i != 0) && isPrime<n, i + 1>();
}


template<size_t i, size_t counter = 0, size_t k = 3, typename Enabled = void>
struct prime;

template<size_t i, size_t counter, size_t k>
struct prime<i, counter, k, typename std::enable_if<isPrime<k>() && counter < i>::type>
{
    static constexpr size_t atIndex() noexcept
    {
        return prime<i, counter + 1, k + 1>::atIndex();
    }
};

template<size_t i, size_t counter, size_t k>
struct prime<i, counter, k, typename std::enable_if<!isPrime<k>() && counter < i>::type>
{
    static constexpr size_t atIndex() noexcept
    {
        return prime<i, counter, k + 1>::atIndex();
    }
};

template<size_t i, size_t counter, size_t k>
struct prime<i, counter, k, typename std::enable_if<isPrime<k>() && counter >= i>::type>
{
    static constexpr size_t atIndex() noexcept
    {
        return k - 1;
    }
};

template<size_t i, size_t counter, size_t k>
struct prime<i, counter, k, typename std::enable_if<!isPrime<k>() && counter >= i>::type>
{
    static constexpr size_t atIndex() noexcept
    {
        return k - 1;
    }
};

template<int until, int step, template<int> typename op, int i = 0>
struct constFor
{
    constFor()
    {
        op<i>{};
        constFor<until, step, op, i + step>{};
    }
};

template<int until, int step, template<int> typename op>
struct constFor<until, step, op, until>
{
    constFor() = default;
};

template<int i>
struct print
{
    print()
    {
        std::cout << prime<i>::atIndex() << std::endl;
    }
};

int main()
{
    constFor<131, 1, print>{};
}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Gian Laager
  • 474
  • 4
  • 14
  • "this list contains 1000 prime numbers" Note however that a product of primes 2 through 29 already overflows `uint32_t`. If your program needs more than 2 through 23, you likely have an integer overflow already. – Igor Tandetnik Oct 09 '21 at 03:18
  • @IgorTandetnik i know but the purpose of this is not to have as many layers of inheritance as possible. This will be used in many different class hierarchies and there for should not be a problem. And have you ever had 29 or more layers of inheritance? – Gian Laager Oct 09 '21 at 07:09
  • If you search for "C++ compile time counter", or "c++ stateful metaprogramming", there are some approaches, e.g. [1](https://stackoverflow.com/questions/6166337/does-c-support-compile-time-counters) [2](https://www.copperspice.com/pdf/CompileTimeCounter-CppCon-2015.pdf) [3](https://stackoverflow.com/questions/44267673/is-stateful-metaprogramming-ill-formed-yet) (not an endorsement, I haven't tried any of them). It is my understanding that the C++ committee frowns upon such techniques and [explores ways of making them not work](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2118) – Igor Tandetnik Oct 09 '21 at 14:10

0 Answers0