Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dynamic_cast to derived type with unknown template argument

Tags:

c++

templates

I would like to dynamic_cast to a templated derived type, with unknown template argument:

struct A {
    virtual void f() { };
};

template <std::size_t N>
struct B : public A { };

With the known template argument the cast can be performed as:

const A& base_ref = B<N>();
const B<N>& ref = dynamic_cast<const B<N>&>(base_ref);

My question is about the case where the value of the template argument (N) is not known.

I was wondering if there is a way to obtain the object type, in this case B<N> from the base class pointer/reference, such that the dynamic_cast template parameter can be inferred automatically?

P.S. In the considered case the template argument is an integer type, and is used as a template parameter for an std::array.

like image 766
Peter Avatar asked Nov 08 '25 04:11

Peter


2 Answers

If you have just a few possible values of N, then you can try dynamic_cast'ing to each of them. Otherwise, the answer is:

No. As a compile time constant, no. If you don't need a compile time constant, then you can do this:

struct AWithN {
  int n;

  AWithN(int n_) : n(n_) { }
};

template <std::size_t N>
struct B: public AWithN {
  B() : AWithN(N) { }
};

And you can dynamic_cast to AWithN, from where you can get n.

like image 144
geza Avatar answered Nov 10 '25 19:11

geza


OK, this is going to be nasty.

You can automate the lookup of a valid dynamic cast, if:

  1. You cap the maximum value of N in B<N> to a value your compiler will be happy with.

  2. You provide a visitor taking a template argument in order to action the result of a successful cast.

  3. You don't mind sequential lookups (although further work here[1] would involve building a cache of successful lookups to improve runtime).

[1] further work would obviously be a waste of time. The time would be better spent working on a better design.

#include <type_traits>
#include <vector>
#include <iostream>
#include <utility>
#include <array>
#include <algorithm>
#include <stdexcept>

struct A
{
    virtual ~A() = default;
};

template<std::size_t N>
struct B : A
{

};

template<std::size_t I>
struct cast_test
{
    static bool test(A* p) {
        return dynamic_cast<B<I>*>(p) != nullptr;
    }
};

template<std::size_t...Is>
constexpr auto make_cast_tests(std::index_sequence<Is...>)
{
    return std::array<bool(*)(A*), sizeof...(Is)> {
        &cast_test<Is>::test...
    };
}

template<class Visitor, std::size_t I>
struct caller
{
    // figuring out a common return type is a whole new challenge...
    static void call(Visitor& visitor, A* p) {
        return visitor(static_cast<B<I>*>(p));
    }
};

template<class Visitor, std::size_t...Is>
constexpr auto make_callers(std::index_sequence<Is...>)
{
    return std::array<void(*)(Visitor&, A*), sizeof...(Is)> {
        &caller<Visitor, Is>::call...
    };
}

template<std::size_t N, class Visitor, class sequence_type = std::make_index_sequence<N>>
decltype(auto) dynamic_visit(Visitor&& visitor, A*p)
{
    constexpr auto tests = make_cast_tests(sequence_type());
    auto ipos = std::find_if(std::begin(tests), std::end(tests), [&p](auto&& f){ return f(p); });
    if (ipos == std::end(tests))
    {
        throw std::logic_error("increase the range of N");
    }

    constexpr auto callers = make_callers<Visitor>(sequence_type());
    auto icaller = std::begin(callers) + std::distance(std::begin(tests), ipos);
    return (*icaller)(visitor, p);
}

struct visitor
{
    template<std::size_t I>
    void operator()(B<I>* p) const
    {
        std::cout << "I is " << I << std::endl;
    }

};

auto make_an_A(int argc) -> A*
{
    if (argc > 1) {
        return new B<6>;
    }
    else {
        return new B<7>;
    }
}
int main(int argc, char** argv)
{
    A* p = make_an_A(argc);
    dynamic_visit<100>(visitor(), p);
}

expected result when invoked with ./a.out:

I is 7

expected result when invoked with ./a.out "foo":

I is 6
like image 21
Richard Hodges Avatar answered Nov 10 '25 17:11

Richard Hodges



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!