5

Ho la seguente struttura di classeproblemi con specializzazioni modello parziali

// file foo.h: 

struct foo_base 
{ ... } 

template<typename T> struct foo : foo_base 
{ ... }; 

template<typename F> 
using is_foo = std::is_convertible<F,foo_base>; 

template<typename, typename=void> struct aux; 

template<typename Foo> 
struct aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type> 
{ ... };   // specialisation for any foo 

// file bar.h: 
#include "foo.h" 

template<typename T> struct bar : foo<T> 
{ ... }; 

template<typename T> 
struct aux<bar<T>> 
{ ... };   // specialisation for bar<T> 

Ora, il problema è che per aux<bar<T>> entrambe le specializzazioni previste aux sono vitali. C'è un modo per evitare questa disambiglianza senza fornire un'altra specializzazione per ogni T? Si noti che le modifiche al file foo.h non devono conoscere il file bar.h.

Nota L'ambiguità sono regolate in modo tale che la specializzazione prevista nel file di bar.h viene raccolto per qualsiasi aux<bar<T>>. Originariamente, bar non era un modello e la specializzazione aux<bar> non era parziale e quindi preferita. Il problema è sorto rendendo il modello bar.

+1

Come viene definito 'is_foo'? Può fornire un [SSCCE] (http://sscce.org)? – Praetorian

+0

Come si desidera risolvere l'ambiguità? – Casey

+0

@Praetorian vedere le modifiche. 'is_foo > :: value' è' true' (altrimenti non c'era ambiguità) – Walter

risposta

4

Il compilatore non vede struct aux<bar<T>> come più specializzato di struct aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type> a causa del secondo argomento del modello. È possibile specificare il secondo argomento allo stesso modo nella vostra bar<T> specializzazione:

template<typename T> 
struct aux<bar<T>, typename std::enable_if<is_foo<bar<T>>::value>::type> 
{ }; 

Le regole per quanto specializzata specializzazioni template parziali sono complicate, ma cercherò di spiegare molto brevemente:

I tre (il vostro due, più il mio uno) specializzazioni rilevanti sono

template<typename Foo> 
struct aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type> 

template<typename T> 
struct aux<bar<T>> // or aux<bar<T>, void> 
{ }; 

template<typename T> 
struct aux<bar<T>, typename std::enable_if<is_foo<bar<T>>::value>::type> 
{ }; 

per lo standard (14.5.5.2), per determinare quale delle specializzazioni parziali di modello di classe è la più specializzata, la questione che deve essere risolta è quale delle seguenti sovraccarichi modello di funzione sarebbe la migliore corrispondenza in una chiamata a f(aux<bar<T>>()):

template<typename Foo> 
void f(aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type>); // 1 

template<typename T> 
void f(aux<bar<T>>); // or aux<bar<T>, void> // 2 

template<typename T> 
void f(aux<bar<T>, typename std::enable_if<is_foo<bar<T>>::value>::type>); // 3 

E lì, a sua volta, le regole di ordinamento parziale per le funzioni dicono che 1 non è più specializzata di 2, e che 2 non è più specializzato di 1, grosso modo, perché 1 non è chiaramente più specializzato di 2, e 2 non è chiaramente più specializzato di 1. "Chiaramente più specializzato" non è come lo standard lo dice, ma che essenzialmente significa che in base agli argomenti di tipo di uno di questi, gli argomenti tipo dell'altro non sono deducibili.

Quando si confrontano 1 e 3, tuttavia, gli argomenti di 1 sono deducibili da 3: Foo possono essere dedotti come bar<T>. Pertanto, 3 è almeno specializzato quanto 1. Tuttavia, gli argomenti di 3 non sono deducibili da 1: T non possono essere dedotti affatto. La conclusione del compilatore è quindi che 3 è più specializzata rispetto 1.

+0

Wow. Sembra funzionare. Ma non capisco come/perché. Se mi specializzo semplicemente come 'struct aux , void>' non funziona. Forse puoi dare qualche altra spiegazione/intuizione. – Walter

+0

@Walter Ho tentato di spiegare, ma è un argomento molto complicato e potrei aver commesso degli errori. Si prega di dare un'occhiata e vedere se aiuta. – hvd

+0

@hvd +1 ma vedi la mia soluzione per un modo per consentire anche a ulteriori classi derivate di adattarsi al modello. – TemplateRex

2

specializzazione parziale di modelli di classe si basa su pattern matching. Al contrario, la personalizzazione dei modelli di funzione si basa su deduzione argomento modello e risoluzione sovraccarico.

A causa della classe gerarchia presente nel vostro problema, personalizzando comportamento sarebbe in linea di principio essere più conveniente attraverso template overloading di funzioni, perché in grado di prendere derivato-to-base conversioni in considerazione. La corrispondenza del modello utilizzata nella specializzazione del modello di classe parziale non offre la stessa flessibilità.

Tuttavia, dal C++ 11, è possibile eseguire la deduzione del tipo di reso in fase di compilazione. Ecco una soluzione che combina tag di dispacciamento, costruttori predefiniti e decltype tipo deduzione: (! C++ 14 modalità non è richiesta)

#include <iostream> 

// file foo_base.h: 

struct foo_base 
{ 
    foo_base() = default; 
}; 

foo_base faux(foo_base const&) 
{ 
    return foo_base{}; 
} 

template<class T, class = decltype(faux(T{}))> 
struct aux; 

template<class T> 
struct aux<T, foo_base> 
{ 
    enum { value = 1 }; 
}; 

// file foo.h: 

template<typename T> 
struct foo : foo_base 
{ 
    foo() = default; 
}; 

// file bar.h: 

template<typename T> 
struct bar : foo<T> 
{ 
    bar() = default; 
}; 

template<class T> 
bar<T> faux(bar<T> const&) 
{ 
    return bar<T>{}; 
} 

template<class T, class U> 
struct aux<T, bar<U>> 
{ 
    enum { value = 2 }; 
}; 

// file meow.h 

template<class T> 
struct meow : bar<T> 
{ 
    meow() = default;  
}; 

int main() 
{ 
    std::cout << aux<foo_base>::value; // 1 
    std::cout << aux<foo<int>>::value; // 1 
    std::cout << aux<bar<int>>::value; // 2 
    std::cout << aux<meow<int>>::value; // 2 
} 

Live Example che funziona sia con g ++ e clang in C++ 11 modalità .

La funzione di constexprfaux() è sovraccarico per foo_bar e come un modello di funzione per bar<T>. Qualsiasi argomento la cui classe è derivata da foo_base ma non da bar<T> selezionerà il precedente sovraccarico e qualsiasi cosa derivata da bar<T> selezionerà il secondo sovraccarico. Questo meccanismo è lo stesso di ad es. nella Libreria standard in cui le categorie iteratore vengono utilizzate per la spedizione di tag diverse implementazioni di std::advance() ad es.

Per utilizzare questo meccanismo di selezione durante la specializzazione parziale del modello di classe aux, sono necessari altri due ingredienti. Innanzitutto, tutte le classi devono avere un costruttore predefinito . In secondo luogo, decltype() viene applicato a tale espressione faux(T{}) a dedurre il tipo di reso.

NOTA: non è necessario che faux() è constexpr o che uno dei costruttori di default sono constexpr, perché decltype() sarà in realtà non valutare la chiamata di funzione, ma solo dedurre il tipo restituito .

Il modello di classe principale aux ha un argomento modello predefinito:

template<class T, class = decltype(faux(T{}))> 
struct aux; 

parzialmente specializzata il secondo argomento su foo_base permette di fornire il comportamento per qualsiasi classe che deriva da foo_base:

template<class T> 
struct aux<T, foo_base> 
{ // custom behavior for anything derived from foo_bar }; 

La seconda specializzazione parziale corrisponde a qualsiasi classe derivata da qualsiasi istanza di modello bar<U>

template<class T, class U> 
struct aux<T, bar<U>> 
{ // custom behavior for anything derived from bar<U> for some U } 

NOTA: l'inconveniente principale è che si potrebbe essere necessario fornire un costruttore di default per tutte le classi nella gerarchia. Questo può o non può essere un ostacolo che puoi superare. La maggior parte delle classi ha già un costruttore predefinito, ma alcuni potrebbero non farlo. In questo senso, questa soluzione è invadente (vale a dire che non può essere fissata al di sopra del codice esistente, ma richiede la modifica di quel codice).

+0

Risposta intelligente. Si presuppone che le classi derivate da 'bar' debbano essere abbinate, anche se il codice dell'OP non le corrisponderebbe, ma potrebbe essere una presunzione corretta. – hvd

+0

@hvd Ho appena scoperto che 'constexpr' non è nemmeno richiesto, perché' decltype' non valuta effettivamente la chiamata alla funzione, ma deduce solo il tipo restituito! – TemplateRex

+0

@hvd è abbastanza facile abbinare solo 'bar ', è sufficiente fornire una specializzazione parziale 'aux >'. – TemplateRex

Problemi correlati