5

sagen, dass ich eine einfache nullary Template-Funktion auf einem einzigen Parameter, mit zwei Spezialisierungen, einem für unsigned long und einen für size_t (Inhalt nicht wichtig) als Templat habe:Kann eine globale Funktionsdefinition unter Verwendung der Template-Metaprogrammierung bedingt deaktiviert werden?

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

Mein Verständnis ist, dass die genaue Definition der Typ size_t ist plattformabhängig und kann daher unsigned long entsprechen oder nicht. Auf meiner aktuelle Plattform (Cygwin g ++ 5.2.0 unter Windows 10 64-Bit-Kompilierung mit -std=gnu++1y) diesen beiden Typen äquivalent zu sein scheinen, so dass der obige Code kompilieren fehlschlägt:

../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; } 
         ^

Von meinem Denken, dieses Problem konnte gelöst werden, indem einfach die Funktionsdefinition size_t deaktiviert wird, da jeder Code, der versucht hat, f<size_t>() aufzurufen, automatisch in f<unsigned long>() aufgelöst wird. Die Funktion sollte jedoch aktiviert sein, damit sich Plattformen, die size_t definieren, von unsigned long unterscheiden.

Ich habe ein wenig über Metaprogrammierung und SFINAE lesen, und ich habe mit Sachen wie diese gespielt:

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

Aber ich bin nicht sicher, wie solch ein Fragment zu verwenden, um eine global zu deaktivieren Funktionsdefinition, wenn das überhaupt möglich ist.

Gibt es also eine Möglichkeit, eine globale Funktionsdefinition unter Verwendung der Template-Metaprogrammierung bedingt zu deaktivieren? Oder, ganz allgemein, bin ich auf dem richtigen Weg oder auf einem falschen Weg?

+1

@immibis: Wie weiß der Präprozessor, dass 'sizeof (size_t) == sizeof (unsigned long)'? –

+0

Ein Vorbehalt: Selbst wenn 'sizeof (size_t) == sizeof (unsigned long)' ist, ist nicht garantiert, dass sie vom selben Typ sind. 'size_t' könnte auch ein typedef zu 'unsigned long long' sein. – tbleher

+0

@immibis Der Präprozessor verarbeitet nur Text. Es weiß nichts über die Struktur des zugrunde liegenden Textes und kann daher nicht auf C++ - Datentypgrößen predigen. Ich nehme an, wenn die Größen dieser Typen auf allen Plattformen als Präprozessor-Makros definiert wären, dann könnten Sie Präprozessor-Bedingungen verwenden, um sie zu vergleichen, aber ich glaube nicht, dass solche "Standard-Makros" existieren. – bgoldst

Antwort

5

Dies auf jeden Fall funktioniert, aber es ist ein langwieriger Bit und skaliert nicht, dass auch für eine größere Anzahl von Spezialisierungen:

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

Die Idee: nicht spezialisiert, sondern Überlastung und die Überlastungen ermöglichen nur wenn die Signatur geeignet ist (während die anderen gleichzeitig deaktiviert werden).

Um die Wartung besser zu machen, sollten die logischen Prüfungen in eine andere geeignete Klasse ausgelagert werden.


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

Er druckt:

1 
2 
2 
1 

So ist es "size_t == unsigned long" auf coliru scheint.

+0

Wenn "size_t" und "unsigned long" vom selben Typ sind, ist die dritte Funktionsvorlage ein ungültiger NDR. –

+0

@ T.C .: Können Sie bitte ausarbeiten? Was ist "NDR"? In meinem [Tests] (http://coliru.stacked-crooked.com/a/b596b5b10651643e) funktionierte es tatsächlich, aber das könnte eine Compiler-Sache sein. – davidhigh

+0

["Das Programm ist schlecht geformt, keine Diagnose erforderlich, wenn: keine gültige Spezialisierung für eine Vorlage generiert werden kann und diese Vorlage nicht instanziiert wird"] (http://eel.is/c++draft/temp.res # 8). –

4

Aus meiner Erfahrung: Nicht mit globalen Funktionen direkt (Lesen von davidhigh's Antwort beim Tippen: ok, es funktioniert, aber wie er sagte, skaliert es nicht gut). SFINAE funktioniert nur, wenn beim Auflösen der Template-Parameter der "Fehler" erscheint. Da C++ zulässt, dass Funktionsvorlagen nur vollständig spezialisiert sind, gibt es kein "Auflösen", wenn der Compiler versucht, eine Spezialisierung zu kompilieren.

jedoch mit Klassen ermöglicht es dem Compiler für die partielle Spezialisierungen und Sie können so etwas tun, was den Vorteil hat, dass Sie die SFINAE-Ausdruck müssen nur für die size_t (ein mySize hier verwenden, da ich es ändern kann):

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

Ausgang mit typedef unsigned long mySize;:

1 
2 
2 

Ausgang mit einem anderen typedef (na ja, nicht aus offensichtlichen Gründen int):

Try it online

+1

Für eine größere Anzahl von Spezialisierungen würde ich mit diesen Lösungen gehen (und meine Antwort nur verwenden, wenn es nicht komplizierter als im OP wird), also +1. Ich würde es weiter in ein "Namespace-Detail" einbinden und eine andere Funktion außerhalb verwenden, die das Klassenmitglied aufruft. – davidhigh

+1

Manchmal löse ich den schwierigen Teil und überwache das Offensichtliche - natürlich bietet eine globale Funktion, die die Klassenmitglieder aufruft, sogar die einfache Syntax einer globalen Funktion ... – Anedar

2

Hier ist ein Ansatz, der ein bisschen seltsam ist, aber ziemlich einfach, mit zu arbeiten:

//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"; 
} 

Die hier Idee ist, dass Sie eine bestimmte Funktion für den size_t Fall machen können, die nicht bevorzugt sein, wird aber verwendet, wenn es keine andere Option gibt. Wenn size_t und MyType gleich sind, wird die MyType Überladung verwendet, andernfalls wird die size_t Überladung verwendet.

f Anrufe f2 und verwendet einen nachgestellten Rückgabetyp mit decltype so, dass, wenn f2, nicht für einen bestimmten Typ T existieren, dann wird f auch nicht existieren.

Mit dieser Technik können Sie auch Überladungen für andere Typen hinzufügen.

0

Ich habe @ davidhighs Antwort akzeptiert, weil ich denke, dass es die am besten geeignete Lösung für meine Frage ist, aber in meinem tatsächlichen Code habe ich eine andere Lösung verwendet und nur für den Fall, dass es anderen hilft, werde ich Beschreibe es hier.

Meine Lösung basiert auf einem Kommentar von @immibis, der leider gelöscht wurde. Es war so etwas wie "Kann man das nicht einfach mit dem Präprozessor machen?" Ich erkannte, dass die C *_MAX Makros von climits tatsächlich verwendet werden können, und die Lösung ist sehr einfach. Danke an @immibis!

Ich habe einen Präprozessor Guard auf alle Typen angewendet, die einen Konflikt verursachen können, sowohl für signierte als auch für unsignierte Varianten. Dieser bestand aus size_t, uintmax_t, ssize_t, ptrdiff_t und intmax_t.

Auch, wie @tbleher in seinem Kommentar darauf hingewiesen hat, können manchmal nominale Typen gleicher Größe unterschiedliche wahre Typen sein, das Beispiel ist unsigned long und unsigned long long. In der Tat, auf meinem aktuellen System sizeof(unsigned long) == sizeof(unsigned long long) == 8, und dito für die signierten Varianten. Obwohl sie die gleiche Größe haben, werden sie als verschiedene echte Typen betrachtet und stehen nicht in Konflikt.

Mein Ansatz bestand darin, zuerst eine Funktion für jeden der garantiert unterschiedlichen Typen zu definieren, dann eine konzeptuelle Sortierung für die "konfliktiven" Typen zu definieren und dann schrittweise eine Definition für jeden Konflikttyp zu initialisieren, dessen Größe sowohl (1) größer als die Größe von [unsigned] long long und (2) nicht gleich der Größe eines Konflikttyps, der früher in der Bestellung sitzt.

Hier ist eine 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 

Ausgang auf meinem System:

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

So wie sich herausstellte, auf meinem System, alle konflikt Typen mit den echten Typen in der Tat Konflikt tun unsigned long und long.

Ein paar Einschränkungen dieser Lösung sind, dass es nur für Typen mit entsprechenden *_MAX Makros funktionieren kann, und es funktioniert nicht für Gleitkommatypen, da der Präprozessor Gleitkomma-Arithmetik und Vergleiche nicht unterstützt.