2016-05-09 16 views
5

Biorąc pod uwagę następujący fragment kodu:czasie kompilacji wysyłkowy: uzależnione od ważnej rozmowy

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

W zaproszeniu get(), chciałbym wykonać w czasie kompilacji wysyłkę do odpowiedniej obsługi. Podstawową ideą jest to, że ObjectType jest znane albo przez GroupA lub przez GroupB. Moje pierwsze podejście było następujące:

template<typename ObjectType> 
ObjectType get() 
    { 
    return Dispatch<ObjectType, GroupA, GroupB>::get(*this); 
    } 

z:

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

przypuszczałem to będzie działać myśląc, że zastąpienie ObjectType w klauzuli enable_ifis_same doprowadzi jedno z wyrażeń na niepowodzenie, a zatem pozostawiając tylko jedna ważna specjalizacja. Jednak niejednoznaczne nazwy i błędy re-definition mnie źle.

Dlaczego moje rozumowanie jest nieprawidłowe? Jak mogę zamiast tego poprawnie wysłać połączenie?

+0

@JoachimPileborg: Dzięki, to była literówka wprowadzona podczas upraszczania imion. Naprawiono to. – OnMyLittleDuck

+2

Należy również zauważyć, że '_ObjectSpaceHolder' jest zarezerwowany dla kompilatora (wraz ze wszystkimi nazwami, które rozpoczynają podkreślenie-litera Capital). –

+0

Domyślam się, że powyższym założeniem jest, że tylko jeden z 'szablonu T GroupA :: get ()' i 'template T GroupB :: get ()' istnieje - czy to na pewno prawda? – Smeeheey

Odpowiedz

2
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...>; 

trwa to szablon i listę argumentów, i informuje, czy można je zastosować.

szablon, który ocenia foo.get<Bar>():

template<class ObjectType, class Source> 
using get_template_result = decltype(std::declval<Source>().get<ObjectType>()); 

możemy wywołać powyższy szablon ważnie?

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

Pakiet umieścić szablon do typu, który pozwala nam ocenić go:

template<template<class...>class Z> 
struct z_template { 
    template<class...Ts> 
    using result = Z<Ts...>; 
}; 

Podobny pakiet, który odrzuca ich argumenty i zwraca wynik zawsze:

template<class Result> 
struct z_identity { 
    template<class...>using result=Result; 
}; 

Ocenia, jeśli to możliwe, get_template_result. Jeśli tak, porównaj jego typ z ObjectType. W przeciwnym razie, porównuje ObjectType* z ObjectType (gwarantowane 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> 
>; 

Gdy mamy wszystko, co możemy oznaczyć wysyłkę!

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>{}); 
} 

teraz get_smart<ObjectType, TypeA, TypeB>(something) wyszuka listę TypeA następnie TypeB aż znajdzie typu można nazwać .get<ObjectType>() na i zwraca ObjectType. Potem się zatrzymuje.

Jeśli taki typ nie zostanie znaleziony, nie zostanie skompilowany.

Jesteś odpowiedzialny za ustawienie wartości r/l listy typów TypeA TypeB i ObjectType. Długość listy ograniczona jest granicami rekursji szablonów (zwykle w latach 100).

+0

Jak schludnie! Dziękuję, wiele się nauczyłeś! – OnMyLittleDuck

0

Co

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

?

Twoje założenie wydaje mi się dość rygorystyczne i skompilowałem twój kod (dodając kulę z template, zobacz "p.s."), ale uważam, że jest zbyt skomplikowany.

p.s .: template przed get() jest wymagany przez mój clang ++; moje g ++ nie wymaga tego, ale akceptuje je. Przypuszczam, że powinieneś dodać to także do swojej wersji.

p.s.2: przepraszam za mój zły angielski.

--- EDIT ---

Myślenie lepiej, moje rozwiązanie jest zbyt skomplikowane też.

Co o prostszej

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

?

Jeśli intencją jest, aby mieć pewność, że jest GroupAObjectType lub GroupB (i innych typów, jeśli przyzwyczajenie rozszerzyć rozwiązanie innych typów) można napisać coś, co powiedzieć, jeśli typ jest podany w liście o zmiennej liczbie argumentów; coś

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

i przedefiniować get() mieć pewność (poprzez SFINAE), które ObjectType znajduje się na liście constitued przez GroupA i GroupB; coś

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

Dziękuję za rozpatrzenie tego, ale wydaje się, że moje intencje nie były jasne, więc zaktualizuję odpowiedź odpowiednio.Podsumowując: gdy zapytanie dotyczy określonego typu ObjectType, get powinien wywołać wywołanie do obiektu GroupAvent lub do grupy GroupB. Gwarantuje się, że tylko jedno z tych połączeń jest ważne. Tak więc szukam sposobu na wyrażenie tej warunkowej wysyłki. – OnMyLittleDuck

+0

Niestety nie mówię poprawnie po angielsku, więc to z pewnością moja wina i nie jest prawdą, że nie jesteś wystarczająco jasny. Przepraszam za to. Ale ... to, co rozumiem z twojego nowego wyjaśnienia, zbiega się z tym, co zrozumiałem z poprzedniego. Teraz nie rozumiem w szczególności, czy proponowane przeze mnie hipotezy są odpowiednie, a jeśli nie, dlaczego. – max66

1

Jeśli można użyć C++ 14, static_if wygląda czystym roztworze:

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); 
} 

Obie gałęzie muszą być parsowalnym, ale tylko oddział podjęte zostaną faktycznie instancja.

Napisałem a tutorial on static_if dla spotkania C++ 2015. Powinno wystarczyć, aby zrozumieć, jak to działa i napisać własną implementację.

Mam również napisane an implementation here.

Obie implementacje oparte są na this CppCoreGuidelines issue.

Powiązane problemy