Sending compilation time: conditional call

Given the following code snippet:

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 would like to send the compile time to the appropriate handler. The basic idea is that ObjectType is known by either GroupA or GroupB . My initial approach was as follows:

 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 assumed that this would work, thinking that replacing the ObjectType in the is_same enable_if is_same would cause one of the expressions to fail and therefore leave only one valid specialization. However, ambiguous names and redefinition errors prove that I am wrong.

Why are my arguments wrong? And how can I correctly send a call instead?

+5
source share
3 answers
 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 argument list 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 call the pattern above?

 template<class ObjectType, class Source> using can_get_template = can_apply< get_template_result, ObjectType, Source >; 

A package for placing a template in a type that allows us to 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 the 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, ObjectType* is compared 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> >; 

As soon as we have it all, we can mark the shipment!

 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 in the TypeA list, then TypeB until it finds a type that you can call .get<ObjectType>() , and returns ObjectType . Then he stops.

If this type is not found, it will not compile.

You are responsible for setting the r / l value for the type list TypeA TypeB and ObjectType . The list length is limited by the template recursion boundaries (usually 100 s).

+2
source

If you can 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 should be subject to analysis, but in fact only the accepted branch will be created.

I wrote a static_if tutorial for Meeting C ++ 2015. Just understand how this works and write your own implementation.

I also wrote an implementation here .

Both implementations are based on this issue of CppCoreGuidelines .

+1
source

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 complicated to me, and I will compile your code (adding coulple from template , see the following "ps"), but I think it is too complicated.

ps: template before get() requested by my clang ++; My g ++ does not require this, but accepts it. I suppose you should also add it to your version.

ps2: sorry for my bad english.

--- EDIT ---

Thinking better, my decision is too complicated.

How about a simpler

  template<typename ObjectType> ObjectType get() { return static_cast<_ObjectSpaceHolder<ObjectType> &>(*this).m_objectSpace.template get<ObjectType>(); } 

?

If you intend to be sure that ObjectType is GroupA or GroupB (and other types, if you will not extend the solution to other types), you can write something that says if the type is in the variational list; sort of

 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 override get() to make sure (via SFINAE) that ObjectType is in the list compiled by GroupA and GroupB ; sort of

  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>(); } 
0
source

All Articles