2016-05-09 12 views
5

Dato il seguente pezzo di codice:Compile tempo della spedizione: subordinata valido chiamata

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

Nella chiamata get(), vorrei eseguire un tempo della spedizione di compilazione al gestore appropriato. L'idea di base è che ObjectType è noto o per GroupA o per GroupB. Il mio approccio iniziale era la seguente:

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

con:

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

Avevo pensato che avrebbe funzionato a pensare che sostituendo ObjectType nella clausola is_same di enable_if porterebbe una delle espressioni a fallire e lasciando quindi solo una singola specializzazione valida. Tuttavia, nomi ambigui e errori di ridefinizione mi dimostrano un errore.

Perché il mio ragionamento è errato? E come posso inviare correttamente la chiamata?

+0

@JoachimPileborg: Grazie, è stato introdotto un errore di battitura durante la semplificazione dei nomi. Risolto questo. – OnMyLittleDuck

+2

Nota anche che '_ObjectSpaceHolder' è riservato al compilatore (insieme a tutti i nomi che iniziano con il carattere di sottolineatura-lettera maiuscola). –

+0

Immagino che l'ipotesi di cui sopra sia che solo uno dei 'template T GroupA :: get ()' e 'modello T GroupB :: get ()' esiste - è proprio vero? – Smeeheey

risposta

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

questo richiede un modello e un elenco di argomenti, e ti dice se è possibile applicare.

Un modello che valuta foo.get<Bar>():

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

Possiamo invocare il modello di cui sopra validamente?

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

Un pacchetto di mettere un modello in un tipo, che ci permette di valutare che:

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

un pacchetto simile, che scarta i suoi argomenti e restituisce il risultato sempre:

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

Valuta get_template_result se possibile. In tal caso, confronta il suo tipo con ObjectType. In caso contrario, si confronta con ObjectType*ObjectType (garantito 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> 
>; 

Una volta che abbiamo tutto questo, siamo in grado di etichettare la spedizione!

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

ora get_smart<ObjectType, TypeA, TypeB>(something) cercherà la lista TypeA poi TypeB finché non trova un tipo è possibile chiamare .get<ObjectType>() su e restituisce ObjectType. Quindi si ferma.

Se non viene trovato alcun tipo, non riesce a compilare.

È responsabilità dell'utente impostare il valore r/l dell'elenco dei tipi Tipo A TipoB e di ObjectType. La lunghezza dell'elenco è limitata dai limiti di ricorsione del modello (in genere negli anni 100).

+0

Che bello! Grazie, ho imparato molto da questo! – OnMyLittleDuck

0

Che dire

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

?

La tua ipotesi mi sembra giusta e compilo il tuo codice (aggiungendo un coulple di template; vedi la seguente "p.s.") ma penso che sia troppo complicato.

p.s .: il template prima del get() è richiesto dal mio clang ++; il mio g ++ non lo richiede ma lo accetta. Suppongo che dovresti aggiungerlo anche alla tua versione.

p.s.2: scusa per il mio pessimo inglese.

--- EDIT ---

Pensando meglio, la mia soluzione è troppo complicato troppo.

Che dire di una semplice

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

?

Se l'intenzione è quella di essere sicuri che sia ObjectTypeGroupA o GroupB (e di altri tipi, se voi non estendere la soluzione ad altri tipi) si potrebbe scrivere qualcosa che dire se un tipo è in una lista variadic; qualcosa come

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

e ridefinire get() per essere sicuri (via SFINAE) che ObjectType è nella lista costituito da n GroupA e GroupB; qualcosa come

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

Grazie per aver esaminato questo, ma sembra che il mio intento non sia chiaro, quindi aggiornerò la risposta di conseguenza.Per riepilogare: quando richiesto per uno specifico ObjectType, get deve inviare la chiamata allo spazio oggetti GroupA o al gruppoB. È garantito che solo una di queste chiamate è valida. Quindi sto cercando un modo per esprimere questa spedizione condizionale. – OnMyLittleDuck

+0

Purtroppo non parlo correttamente l'inglese, quindi è certamente colpa mia e non è vero che non sei abbastanza chiaro. Mi dispiace per quello Ma ... quello che capisco dalla tua nuova spiegazione coincide con ciò che avevo capito dal precedente. Quello che non capisco adesso, in particolare, è se le ipotesi che ho proposto siano appropriate e, in caso contrario, perché. – max66

1

Se si può utilizzare C++ 14, static_if sembra una soluzione pulita:

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

entrambi i rami devono essere analizzabile, ma solo il ramo preso saranno effettivamente istanziati.

Ho scritto a tutorial on static_if per Meeting C++ 2015. Dovrebbe essere sufficiente capire come funziona e scrivere la propria implementazione.

Ho anche scritto an implementation here.

Entrambe le implementazioni sono basate su this CppCoreGuidelines issue.