The examples shown below are just toy examples, if you weren’t already able to catch on.
In the code snippet below, how should we implement pair_typeid
?
class Base1{
public:
constexpr virtual ~Base1() noexcept = default;
protected:
constexpr Base1() noexcept = default;
constexpr Base1(const Base1&) noexcept = default;
constexpr Base1(Base1&&) noexcept = default;
constexpr Base1& operator=(const Base1&) noexcept = default;
constexpr Base1& operator=(Base1&&) noexcept = default;
};
class Base2{
public:
constexpr virtual ~Base2() noexcept = default;
protected:
constexpr Base2() noexcept = default;
constexpr Base2(const Base2&) noexcept = default;
constexpr Base2(Base2&&) noexcept = default;
constexpr Base2& operator=(const Base2&) noexcept = default;
constexpr Base2& operator=(Base2&&) noexcept = default;
};
class Derived1_1 final: public Base1{};
class Derived1_2 final: public Base1{};
class Derived2_1 final: public Base2{};
class Derived2_2 final: public Base2{};
/**
* @brief Gets the `typeid(std::pair<T1, T2>)`.
*
* More formally, `pair_typeid(r1, r2) == typeid(std::pair)`
* where `T1` is the dynamic type of the referent of `r1`
* and `T2` is the dynamic type of the referent of `r2`.
*
* @return `const std::type_info&`
*/
constexpr const std::type_info& pair_typeid(const Base1&, const Base2&) noexcept;
If Derived1_1
, Derived1_2
, Derived2_1
, and Derived2_2
were the only classes to use this function, we could use dynamic_cast
or typeid
, but that’s inelegant so we instead use the visitor pattern in the below impl, because it avoids this. Note how we restrict the extent of the double dispatch.
class Base1;
class Base2;
class Derived2_1;
class Derived2_2;
namespace detail{
template <class T>
constexpr const std::type_info& visit_pair_typeid(const Base1&, const T&) noexcept;
}
class Base1{
public:
constexpr virtual ~Base1() noexcept = default;
protected:
constexpr Base1() noexcept = default;
constexpr Base1(const Base1&) noexcept = default;
constexpr Base1(Base1&&) noexcept = default;
constexpr Base1& operator=(const Base1&) noexcept = default;
constexpr Base1& operator=(Base1&&) noexcept = default;
constexpr virtual const std::type_info& visit_pair_typeid(const Derived2_1&) const noexcept = 0;
constexpr virtual const std::type_info& visit_pair_typeid(const Derived2_2&) const noexcept = 0;
template <class T>
friend constexpr const std::type_info& detail::visit_pair_typeid(const Base1&, const T&) noexcept;
};
template <class T>
constexpr const std::type_info& detail::visit_pair_typeid(const Base1& star_this, const T& acceptor) noexcept{return star_this.visit_pair_typeid(acceptor);}
/**
* @brief Gets the `typeid(std::pair)`.
*
* More formally, `pair_typeid(r1, r2) == typeid(std::pair)`
* where `T1` is the cv-unqualified dynamic type of the referent of `r1`
* and `T2` is the cv-unqualified dynamic type of the referent of `r2`.
*
* @return `const std::type_info&`
*/
constexpr const std::type_info& pair_typeid(const Base1&, const Base2&) noexcept;
class Base2{
public:
constexpr virtual ~Base2() noexcept = default;
protected:
constexpr Base2() noexcept = default;
constexpr Base2(const Base2&) noexcept = default;
constexpr Base2(Base2&&) noexcept = default;
constexpr Base2& operator=(const Base2&) noexcept = default;
constexpr Base2& operator=(Base2&&) noexcept = default;
constexpr virtual const std::type_info& accept_pair_typeid(const Base1&) const noexcept = 0;
template
static constexpr const std::type_info& accept_pair_typeid_impl(const T& star_this, const Base1& visitor) noexcept{return detail::visit_pair_typeid(visitor, star_this);}
friend constexpr const std::type_info& pair_typeid(const Base1& r1, const Base2& r2) noexcept{return r2.accept_pair_typeid(r1);}
};
class Derived2_1 final: public Base2{
protected:
constexpr const std::type_info& accept_pair_typeid(const Base1& visitor) const noexcept final{return accept_pair_typeid_impl(*this, visitor);}
};
class Derived2_2 final: public Base2{
protected:
constexpr const std::type_info& accept_pair_typeid(const Base1& visitor) const noexcept final{return accept_pair_typeid_impl(*this, visitor);}
};
class Derived1_1 final: public Base1{
protected:
constexpr const std::type_info& visit_pair_typeid(const Derived2_1&) const noexcept final{return typeid(std::pair<Derived1_1, Derived2_1>);}
constexpr const std::type_info& visit_pair_typeid(const Derived2_2&) const noexcept final{return typeid(std::pair<Derived1_1, Derived2_2>);}
};
class Derived1_2 final: public Base1{
protected:
constexpr const std::type_info& visit_pair_typeid(const Derived2_1&) const noexcept final{return typeid(std::pair<Derived1_2, Derived2_1>);}
constexpr const std::type_info& visit_pair_typeid(const Derived2_2&) const noexcept final{return typeid(std::pair<Derived1_2, Derived2_2>);}
};
A minor benefit of the visitor pattern is that adding more visitors is extremely easy. This allows templatization of the visitor.
class Base1;
class Base2;
class Derived2_1;
class Derived2_2;
namespace detail{
template <class T>
constexpr const std::type_info& visit_pair_typeid(const Base1&, const T&) noexcept;
}
class Base1{
public:
constexpr virtual ~Base1() noexcept = default;
protected:
constexpr Base1() noexcept = default;
constexpr Base1(const Base1&) noexcept = default;
constexpr Base1(Base1&&) noexcept = default;
constexpr Base1& operator=(const Base1&) noexcept = default;
constexpr Base1& operator=(Base1&&) noexcept = default;
constexpr virtual const std::type_info& visit_pair_typeid(const Derived2_1&) const noexcept = 0;
constexpr virtual const std::type_info& visit_pair_typeid(const Derived2_2&) const noexcept = 0;
template <class T>
friend constexpr const std::type_info& detail::visit_pair_typeid(const Base1&, const T&) noexcept;
};
template <class T>
constexpr const std::type_info& detail::visit_pair_typeid(const Base1& star_this, const T& acceptor) noexcept{return star_this.visit_pair_typeid(acceptor);}
/**
* @brief Gets the `typeid(std::pair)`.
*
* More formally, `pair_typeid(r1, r2) == typeid(std::pair)`
* where `T1` is the cv-unqualified dynamic type of the referent of `r1`
* and `T2` is the cv-unqualified dynamic type of the referent of `r2`.
*
* @return `const std::type_info&`
*/
constexpr const std::type_info& pair_typeid(const Base1&, const Base2&) noexcept;
class Base2{
public:
constexpr virtual ~Base2() noexcept = default;
protected:
constexpr Base2() noexcept = default;
constexpr Base2(const Base2&) noexcept = default;
constexpr Base2(Base2&&) noexcept = default;
constexpr Base2& operator=(const Base2&) noexcept = default;
constexpr Base2& operator=(Base2&&) noexcept = default;
constexpr virtual const std::type_info& accept_pair_typeid(const Base1&) const noexcept = 0;
template
static constexpr const std::type_info& accept_pair_typeid_impl(const T& star_this, const Base1& visitor) noexcept{return detail::visit_pair_typeid(visitor, star_this);}
friend constexpr const std::type_info& pair_typeid(const Base1& r1, const Base2& r2) noexcept{return r2.accept_pair_typeid(r1);}
};
class Derived2_1 final: public Base2{
protected:
constexpr const std::type_info& accept_pair_typeid(const Base1& visitor) const noexcept final{return accept_pair_typeid_impl(*this, visitor);}
};
class Derived2_2 final: public Base2{
protected:
constexpr const std::type_info& accept_pair_typeid(const Base1& visitor) const noexcept final{return accept_pair_typeid_impl(*this, visitor);}
};
template <std::size_t N>
class Derived1: public Base1{
protected:
constexpr const std::type_info& visit_pair_typeid(const Derived2_1&) const noexcept final{return typeid(std::pair<Derived1, Derived2_1>);}
constexpr const std::type_info& visit_pair_typeid(const Derived2_2&) const noexcept final{return typeid(std::pair<Derived1, Derived2_2>);}
};
Unfortunately, templatizing the acceptor requires templatizing virtual functions. Workarounds like templatizing base classes don’t solve the problem at hand. So what does, Lemmy?
for my toy example i need runtime polymorphism and virtual inheritance