Reputation: 712
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?
Upvotes: 5
Views: 271
Reputation: 93364
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.
Upvotes: 1
Reputation: 275878
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).
Upvotes: 2
Reputation: 66230
What about
template<typename ObjectType, typename GroupA, typename GroupB>
struct Dispatch;
template<typename GroupA, typename GroupB>
struct Dispatch<GroupA, GroupA, GroupB>
{
template<typename JoinedGroup>
static
GroupA get(JoinedGroup &joinedGroup)
{
return static_cast<_ObjectSpaceHolder<GroupA> &>(joinedGroup).m_objectSpace.template get<GroupA>();
}
};
template<typename GroupA, typename GroupB>
struct Dispatch<GroupB, GroupA, GroupB>
{
template<typename JoinedGroup>
static
GroupB get(JoinedGroup &joinedGroup)
{
return static_cast<_ObjectSpaceHolder<GroupB> &>(joinedGroup).m_objectSpace.template get<GroupB>();
}
};
?
Your assumption seems rigth to me and I compile your code (adding a coulple of template
; see the following "p.s.") but I think is over-complicated.
p.s.: the template
before get()
is requested by my clang++; my g++ does not require it but accepts it. I suppose you should add it to your version too.
p.s.2: sorry for my bad English.
--- EDIT ---
Thinking better, my solution is over-complicated too.
What about a simpler
template<typename ObjectType>
ObjectType get()
{
return static_cast<_ObjectSpaceHolder<ObjectType> &>(*this).m_objectSpace.template get<ObjectType>();
}
?
If you intention is to be sure that ObjectType
is GroupA
or GroupB
(and other types, if you wont extend the solution to other types) you could write something that say if a type is in a variadic list; something like
template <typename T0>
constexpr bool typeIsInList ()
{ return false; }
template <typename T0, typename T1, typename ... Tl>
constexpr bool typeIsInList ()
{ return std::is_same<T0, T1>::value || typeIsInList<T0, Tl...>(); }
and redefine get()
to be sure (via SFINAE) that ObjectType
is in the list constitued by GroupA
and GroupB
; something like
template<typename ObjectType, typename = typename std::enable_if<typeIsInList<ObjectType, GroupA, GroupB>()>::type>
ObjectType get()
{
return static_cast<_ObjectSpaceHolder<ObjectType> &>(*this).m_objectSpace.template get<ObjectType>();
}
Upvotes: 0