Given the following piece of code:
template<typename GroupA, typename GroupB>
class JoinedObjectGroup
: public _ObjectSpaceHolder<GroupA>
, public _ObjectSpaceHolder<GroupB>
{
public:
JoinedObjectGroup(GroupA &groupA, GroupB &groupB)
: _ObjectSpaceHolder<GroupA>(groupA)
, _ObjectSpaceHolder<GroupB>(groupB)
{
}
template<typename ObjectType>
ObjectType get()
{
// Dispatch to appropriate handler: only one of the following actually compiles as
// either GroupA knows about ObjectType or GroupB, but not both. So:
//
// return static_cast<_ObjectSpaceHolder<GroupA> &>(*this).m_objectSpace.get<ObjectType>();
// or
// return static_cast<_ObjectSpaceHolder<GroupB> &>(*this).m_objectSpace.get<ObjectType>();
}
};
In the get()
call, I'd like to perform a compile time dispatch to the appropriate handler. The underlying idea is that ObjectType
is known either by GroupA
or by GroupB
. My initial approach was the following:
template<typename ObjectType>
ObjectType get()
{
return Dispatch<ObjectType, GroupA, GroupB>::get(*this);
}
with:
template<typename ObjectType, typename GroupA, typename GroupB, typename = void>
struct Dispatch;
template<typename ObjectType, typename GroupA, typename GroupB>
struct Dispatch<ObjectType, GroupA, GroupB, typename std::enable_if<std::is_same<ObjectType, decltype(std::declval<GroupA>().template get<ObjectType>())>::value>::type>
{
template<typename JoinedGroup>
static
ObjectType get(JoinedGroup &joinedGroup)
{
return static_cast<_ObjectSpaceHolder<GroupA> &>(joinedGroup).m_objectSpace.get<ObjectType>();
}
};
template<typename ObjectType, typename GroupA, typename GroupB>
struct Dispatch<ObjectType, GroupA, GroupB, typename std::enable_if<std::is_same<ObjectType, decltype(std::declval<GroupB>().template get<ObjectType>())>::value>::type>
{
template<typename JoinedGroup>
static
ObjectType get(JoinedGroup &joinedGroup)
{
return static_cast<_ObjectSpaceHolder<GroupB> &>(joinedGroup).m_objectSpace.get<ObjectType>();
}
};
I had assumed this would work thinking that substituting ObjectType
in the is_same
clause of enable_if
would lead one of the expressions to fail and therefore leaving only a single valid specialization. However, ambiguous names and re-definition errors prove me wrong.
Why is my reasoning incorrect? And how can I properly dispatch the call instead?
namespace details {
template<template<class...>class Z, class always_void, class...Ts>
struct can_apply : std::false_type {};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...> : std::true_type {};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
this takes a template and a list of arguments, and tells you if you can apply them.
A template that evaluates foo.get<Bar>()
:
template<class ObjectType, class Source>
using get_template_result = decltype( std::declval<Source>().get<ObjectType>() );
Can we invoke the above template validly?
template<class ObjectType, class Source>
using can_get_template = can_apply< get_template_result, ObjectType, Source >;
A package to put a template into a type, that lets us evaluate it:
template<template<class...>class Z>
struct z_template {
template<class...Ts>
using result = Z<Ts...>;
};
A similar package, that discards its arguments and returns Result always:
template<class Result>
struct z_identity {
template<class...>using result=Result;
};
Evaluates get_template_result
if possible. If so, compares its type with ObjectType
. Otherwise, compares ObjectType*
with ObjectType
(guaranteed false):
template<class ObjectType, class Source>
using get_template_gets_type = std::is_same<ObjectType,
typename // maybe?
std::conditional_t<
can_get_template<ObjectType,Source>,
z_template<get_template_result>,
z_identity<ObjectType*>
>::template result<ObjectType, Source>
>;
Once we have all that, we can tag dispatch!
template<class ObjectType, class T0, class...Ts, class Source>
ObjectType get_smart( Source&& source, std::true_type ) {
return static_cast<T0&&>(std::forward<Source>(source)).get<ObjectType>();
}
template<class ObjectType, class T0, class T1, class...Ts, class Source>
ObjectType get_smart( Source&& source, std::false_type ) {
return get_smart<ObjectType, T1, Ts...>(std::forward<Source>(source), get_template_gets_type<ObjectType, T1>{} );
}
template<class ObjectType, class T0, class...Ts, class Source>
ObjectType get_smart( Source&& source ) {
return get_smart( std::forward<Source>(source), get_template_gets_type<ObjectType, T0>{} );
}
now get_smart<ObjectType, TypeA, TypeB>( something )
will search the list TypeA
then TypeB
until it finds a type you can call .get<ObjectType>()
on and returns ObjectType
. Then it stops.
If no such type is found, it fails to compile.
You are responsible to set the r/l value-ness of the list of types TypeA TypeB and of ObjectType
. The length of the list is bounded by template recursion limits (usually in the 100s).
If you can you use C++14, static_if
looks like a clean solution:
template<typename ObjectType>
auto get()
{
using is_group_a = std::is_same
<
ObjectType,
decltype(std::declval<GroupA>().template get<ObjectType>())
>;
return static_if(is_group_a{})
.then([](auto& x_this)
{
return static_cast<_ObjectSpaceHolder<GroupA> &>(x_this)
.m_objectSpace.get<ObjectType>();
})
.else_([](auto& x_this)
{
return static_cast<_ObjectSpaceHolder<GroupB> &>(x_this)
.m_objectSpace.get<ObjectType>();
})(*this);
}
Both branches need to be parseable, but only the taken branch will actually be instantiated.
I have written a tutorial on static_if for Meeting C++ 2015. It should be sufficient to understand how it works and to write your own implementation.
I have also written an implementation here.
Both implementation are based upon this CppCoreGuidelines issue.
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