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.
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.
OK, this is going to be nasty.
You can automate the lookup of a valid dynamic cast, if:
You cap the maximum value of N in B<N> to a value your compiler will be happy with.
You provide a visitor taking a template argument in order to action the result of a successful cast.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With