5

Dire che ho una semplice funzione template nullaria su modelli su un singolo parametro, con due specializzazioni, una per unsigned long, e una per size_t (contenuto non è importante):È possibile disattivare condizionalmente una definizione di funzione globale utilizzando la metaprogrammazione del modello?

template<typename T> T f(void); 
template<> unsigned long f<unsigned long>(void) { return 1; } 
template<> size_t f<size_t>(void) { return 2; } 

mia comprensione è che l'esatta definizione del il tipo size_t dipende dalla piattaforma e quindi potrebbe essere o meno equivalente a unsigned long. Sul mio attuale piattaforma (Cygwin g ++ 5.2.0 su Windows 10 a 64 bit compilazione con -std=gnu++1y) appaiono questi due tipi di essere equivalente, in modo che il codice di cui sopra non riesce a compilare:

../test.cpp:51:19: error: redefinition of ‘T f() [with T = long unsigned int]’ 
template<> size_t f<size_t>(void) { return 2; } 
       ^
../test.cpp:50:26: note: ‘T f() [with T = long unsigned int]’ previously declared here 
template<> unsigned long f<unsigned long>(void) { return 1; } 
         ^

Dal mio pensiero, questo problema potrebbe essere risolto semplicemente disabilitando la definizione della funzione size_t, poiché qualsiasi codice che tentasse di chiamare f<size_t>() si risolverebbe automaticamente su f<unsigned long>(). Ma la funzione dovrebbe essere abilitata per le piattaforme che definiscono size_t diverso da unsigned long.

Ho letto un po 'di template metaprogrammazione e SFINAE, e ho giocato con le cose in questo modo:

std::enable_if<(sizeof(size_t) > sizeof(unsigned long))>::type 

Ma io sono dubbi sull'utilizzo di un tale frammento di disabilitare a livello globale definizione della funzione, se possibile.

Quindi, esiste un modo per disabilitare condizionalmente una definizione di funzione globale utilizzando la metaprogrammazione del modello? O, più in generale, sono sulla buona strada o scendendo lungo un percorso sbagliato?

+1

@immibis: come fa il preprocessore a sapere che 'sizeof (size_t) == sizeof (unsigned long)'? –

+0

Un avvertimento: anche se 'sizeof (size_t) == sizeof (unsigned long)', non è garantito che siano dello stesso tipo. 'size_t' potrebbe anche essere un typedef a' unsigned long long'. – tbleher

+0

@immibis Il preprocessore esegue solo l'elaborazione del testo. Non sa nulla della struttura del testo sottostante e quindi non può prevedere le dimensioni del tipo di dati C++. Suppongo che se le dimensioni di questi tipi fossero definite come macro di preprocessore in modo standard su tutte le piattaforme, allora si potrebbero usare condizionali per il preprocessore per confrontarle, ma non penso che esistano tali "macro standard". – bgoldst

risposta

5

Questo funziona in ogni caso, ma è un po 'noioso e non scala che bene per un numero maggiore di specializzazioni:

template<typename T 
     , std::enable_if_t<!std::is_same<T, unsigned long>::value 
         && !std::is_same<T, size_t>::value>* = nullptr> 
T f() { return 1; } 

template<typename T 
     , std::enable_if_t<std::is_same<T, unsigned long>::value>* = nullptr> 
T f() { return 2; } 

template<typename T 
     , std::enable_if_t<std::is_same<T, size_t>::value 
         && !std::is_same<T, unsigned long>::value>* = nullptr> 
T f() { return 3; } 

L'idea: non specializzati, ma di sovraccarico, e abilitare i sovraccarichi solo se la firma è appropriata (mentre disabilitando gli altri allo stesso tempo).

Inoltre, al fine di renderlo meglio gestibile, è necessario esternalizzare i controlli logici in un'altra classe adeguata.


DEMO:

int main() 
{ 
    std::cout<<f<int>()<<std::endl; 
    std::cout<<f<unsigned long>()<<std::endl; 
    std::cout<<f<size_t>()<<std::endl; 
    std::cout<<f<unsigned long long>()<<std::endl; 
} 

Esso stampa:

1 
2 
2 
1 

Così sembra "size_t == unsigned long" su coliru.

+0

Se 'size_t' e' unsigned long' sono dello stesso tipo, il terzo modello di funzione è NDR malformato. –

+0

@ T.C .: puoi elaborare per favore? Che cos'è "NDR"? Nei miei [test} (http://coliru.stacked-crooked.com/a/b596b5b10651643e) ha funzionato in realtà, ma questo potrebbe essere un problema per i compilatori. – davidhigh

+0

["Il programma è mal formato, non è richiesta alcuna diagnostica, se: non è possibile generare una specializzazione valida per un modello e tale modello non viene istanziato"] (http://eel.is/c++draft/temp.res # 8). –

4

Dalla mia esperienza: non direttamente con le funzioni globali (leggendo la risposta di davidhigh durante la digitazione: ok, funziona, ma come ha detto non si adatta bene). SFINAE funziona solo se viene visualizzato l'errore durante la risoluzione dei parametri del modello. Poiché il C++ consente ai template di funzioni di essere completamente specializzati, non esiste una "risoluzione" quando il compilatore tenta di compilare una specializzazione.

Tuttavia, con le classi il compilatore permette di specializzazioni parziali e si può fare qualcosa di simile, che ha il vantaggio che è necessario lo SFINAE espressione solo per il size_t (utilizzando un mySize qui dato che posso cambiarlo):

#include <iostream> 
#include <type_traits> 
using namespace std; 

typedef unsigned int mySize; 

//default 
template <class P, class dummy = void> 
class T{ 
    public: 
    static P f(){return 0;} 
}; 

//int 
template <> 
class T<int,void> { 
    public: 
    static int f(){return 1;} 
}; 

//unsigned long 
template <> 
class T<unsigned long, void> { 
    public: 
    static unsigned long f(){return 2;} 
}; 

template <class P> 
class T<P, typename enable_if<is_same<P, mySize>::value && !is_same<P, unsigned long>::value, void>::type> { 
    public: 
    static P f(){return 3;} 
}; 

int main() { 
    cout << T<int>::f() << endl; 
    cout << T<unsigned long>::f() << endl; 
    cout << T<mySize>::f() << endl; 
    return 0; 
} 

uscita con typedef unsigned long mySize;:

1 
2 
2 

uscita con qualsiasi altra typedef (bene, non int per ovvie ragioni):

1 
2 
3 

Try it online

+1

Per un numero maggiore di specializzazioni, opterei per queste soluzioni (e uso la mia risposta solo se non diventa più complicata come nel PO), quindi +1. Lo racchiuderei ulteriormente in un 'namespace detail' e uso un'altra funzione al di fuori della quale chiama il membro della classe. – davidhigh

+1

A volte risolvo la parte difficile e controllo l'ovvio - ovviamente, una funzione globale che chiama i membri della classe fornisce anche la sintassi facile di una funzione globale ... – Anedar

2

Ecco un approccio che è un po 'strano, ma abbastanza facile da lavorare:

//using MyType = unsigned int; 
using MyType = unsigned long; 

unsigned long f2(MyType *,int) { return 1; } 
size_t  f2(size_t *,...) { return 2; } 

template <typename T> 
auto f() -> decltype(f2(static_cast<T*>(0),0)) { 
    T* p = 0; 
    return f2(p,0); 
} 

int main() 
{ 
    std::cout << f<MyType>() << "\n"; 
    std::cout << f<size_t>() << "\n"; 
} 

L'idea è che si può fare una funzione distinta per il caso size_t quali no essere preferito, ma verrà utilizzato se non ci sono altre opzioni. Se size_t e MyType sono uguali, verrà utilizzato il sovraccarico MyType, altrimenti verrà utilizzato il sovraccarico size_t.

f chiamate f2 e utilizza un tipo restituito posteriore con decltype modo che se f2 non esiste per un determinato tipo T, allora f non esisterà neanche.

Utilizzando questa tecnica, è possibile aggiungere facilmente sovraccarichi anche ad altri tipi.

0

Ho accettato la risposta di @ davidhigh perché penso che sia la soluzione più appropriata per la mia domanda come richiesto, tuttavia, nel mio codice attuale ho usato una soluzione diversa, e nel caso in cui aiuti gli altri, io descrivilo qui.

La mia soluzione si basa su un commento fatto da @immibis che purtroppo è stato cancellato da allora. Era qualcosa del tipo "Non si può fare facilmente usando il preprocessore?" Mi sono reso conto che è possibile utilizzare i macro C *_MAX da climits e la soluzione è molto semplice. Grazie a @immibis!

Ho applicato una protezione per il preprocessore a tutti i tipi che può causare un conflitto, sia per le varianti firmate che per quelle senza segno. Consisteva in size_t, uintmax_t, ssize_t, ptrdiff_t e intmax_t.

Inoltre, come @tbleher ha sottolineato nel suo commento, a volte i tipi nominali delle stesse dimensioni possono essere diversi tipi reali, l'esempio in questione è unsigned long e unsigned long long. Infatti, sul mio attuale sistema sizeof(unsigned long) == sizeof(unsigned long long) == 8, e idem per le varianti firmate. Anche se hanno le stesse dimensioni, sono considerati diversi tipi reali e non entrano in conflitto.

Il mio approccio è stato quello di definire prima una funzione per ciascuno dei tipi distinti garantiti, quindi definire un ordine concettuale per i tipi "in conflitto" e quindi istanziare progressivamente una definizione per ogni tipo in conflitto la cui dimensione è entrambe (1) maggiore delle dimensioni di [unsigned] long long e (2) non è uguale alla dimensione di qualsiasi tipo in conflitto che si trova prima nell'ordinazione.

Ecco una demo:

#include <climits> // most integer limit macros, including SSIZE_MAX 
#include <cstddef> // size_t, ptrdiff_t, [u]intmax_t 
#include <cstdint> // SIZE_MAX, PTRDIFF_{MIN,MAX}, UINTMAX_MAX, INTMAX_{MIN,MAX} 
#include <sys/types.h> // ssize_t 
#include <cstdio> 

// primary template 
template<typename T> void f(void); 

// declarations -- guaranteed not to cause conflicts; dups are allowed 
template<> void f<unsigned char>(void); 
template<> void f<unsigned short>(void); 
template<> void f<unsigned int>(void); 
template<> void f<unsigned long>(void); 
template<> void f<unsigned long long>(void); 
template<> void f<size_t>(void); 
template<> void f<uintmax_t>(void); 
template<> void f<char>(void); 
template<> void f<short>(void); 
template<> void f<int>(void); 
template<> void f<long>(void); 
template<> void f<long long>(void); 
template<> void f<ssize_t>(void); 
template<> void f<ptrdiff_t>(void); 
template<> void f<intmax_t>(void); 

int main(void) { 
    f<unsigned char>(); 
    f<unsigned short>(); 
    f<unsigned int>(); 
    f<unsigned long>(); 
    f<unsigned long long>(); 
    f<size_t>(); 
    f<uintmax_t>(); 
    f<char>(); 
    f<short>(); 
    f<int>(); 
    f<long>(); 
    f<long long>(); 
    f<ssize_t>(); 
    f<ptrdiff_t>(); 
    f<intmax_t>(); 
    return 0; 
} // end main() 

// definitions -- must use preprocessor guard on conflictable types 
template<> void f<unsigned char>(void) { std::printf("%d\n",1); } 
template<> void f<unsigned short>(void) { std::printf("%d\n",2); } 
template<> void f<unsigned int>(void) { std::printf("%d\n",3); } 
template<> void f<unsigned long>(void) { std::printf("%d\n",4); } 
template<> void f<unsigned long long>(void) { std::printf("%d\n",5); } 
#if SIZE_MAX > ULLONG_MAX 
template<> void f<size_t>(void) { std::printf("%d\n",6); } 
#endif 
#if UINTMAX_MAX > ULLONG_MAX && UINTMAX_MAX != SIZE_MAX 
template<> void f<uintmax_t>(void) { std::printf("%d\n",7); } 
#endif 
template<> void f<char>(void) { std::printf("%d\n",8); } 
template<> void f<short>(void) { std::printf("%d\n",9); } 
template<> void f<int>(void) { std::printf("%d\n",10); } 
template<> void f<long>(void) { std::printf("%d\n",11); } 
template<> void f<long long>(void) { std::printf("%d\n",12); } 
#if SSIZE_MAX > LLONG_MAX 
template<> void f<ssize_t>(void) { std::printf("%d\n",13); } 
#endif 
#if PTRDIFF_MAX > LLONG_MAX && PTRDIFF_MAX != SSIZE_MAX 
template<> void f<ptrdiff_t>(void) { std::printf("%d\n",14); } 
#endif 
#if INTMAX_MAX > LLONG_MAX && INTMAX_MAX != SSIZE_MAX && INTMAX_MAX != PTRDIFF_MAX 
template<> void f<intmax_t>(void) { std::printf("%d\n",15); } 
#endif 

uscita sul mio sistema:

1 
2 
3 
4 
5 
4 
4 
8 
9 
10 
11 
12 
11 
11 
11 

Così come si è scoperto, sul mio sistema, tutti i tipi di conflictable in effetti conflitto con i veri tipi unsigned long e long.

Un paio di limitazioni di questa soluzione sono che può funzionare solo per i tipi che hanno corrispondenti macro *_MAX e non funziona per i tipi a virgola mobile, poiché il preprocessore non supporta l'aritmetica a virgola mobile e i confronti.