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