Teilspezialisierung der C ++ - Funktionsvorlage?

87

Ich weiß, dass der folgende Code eine teilweise Spezialisierung einer Klasse ist:

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

Ich weiß auch, dass C ++ keine teilweise Spezialisierung von Funktionsvorlagen zulässt (nur vollständige ist zulässig). Aber bedeutet mein Code, dass ich meine Funktionsvorlage teilweise auf ein / denselben Typargumente spezialisiert habe? Weil es für Microsoft Visual Studio 2010 Express funktioniert! Wenn nein, können Sie bitte das Konzept der Teilspezialisierung erläutern?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}


int main ()
{
    cout << max(4,4.2) << endl;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}
Narek
quelle
Suchen Sie nach dieser Analogie der Klassenspezialisierung. Wenn es Klassenspezialisierung heißt, warum sollte ich dann das Gleiche für die Funktion als Überladung betrachten?
Narek
1
Nein, die Spezialisierungssyntax ist anders. Schauen Sie sich die (vermeintliche) Funktionsspezialisierungssyntax in meiner Antwort unten an.
Iammilind
2
Warum wird dadurch nicht der Fehler "Aufruf an max ist mehrdeutig" ausgegeben? Wie löst sich max(5,5)auf max(T const&, T const&) [with T=int]und nicht max(T1 const&, T2 const&) [with T1=int and T2=int]?
NHDaly

Antworten:

81

Eine teilweise Teilspezialisierung ist nach Standard noch nicht zulässig. In diesem Beispiel überladen Sie die max<T1,T2>Funktion tatsächlich und spezialisieren sie nicht .
Die Syntax hätte ungefähr so aussehen sollen, wenn es erlaubt gewesen wäre:

// Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{                  ^^^^^ <--- [supposed] specializing here
  return 10;
}

Bei Funktionsvorlagen ist nach C ++ - Standard nur eine vollständige Spezialisierung zulässig - mit Ausnahme der Compiler-Erweiterungen!

iammilind
quelle
1
@ Narek, Partielle Funktionsspezialisierung ist nicht Teil des Standards (aus welchen Gründen auch immer). Ich denke, MSVC unterstützt es als Erweiterung. Möglicherweise wird es nach einiger Zeit auch von anderen Compilern erlaubt.
Iammilind
1
@iammilind: Kein Problem. Das scheint er schon zu wissen. Deshalb versucht er das auch für die Funktionsvorlage. Also habe ich es noch einmal bearbeitet und es jetzt klar gemacht.
Nawaz
19
Wer kann erklären, warum eine teilweise Spezialisierung nicht erlaubt ist?
HelloGoodbye
2
@NHDaly, Es gibt keinen Mehrdeutigkeitsfehler, da eine Funktion besser übereinstimmt als die andere. Warum wählt er (T, T)über (T1, T2)für (int, int), ist , weil die früheren Garantien , dass es 2 Parameter und beiden Typen sind gleich; Letzteres garantiert nur, dass es 2 Parameter gibt. Der Compiler wählt immer eine genaue Beschreibung. zB Wenn Sie zwischen 2 Beschreibungen eines "Flusses" wählen müssen, welche würden Sie wählen? "Sammlung von Wasser" vs "Sammlung von fließendem Wasser".
Iammilind
1
@kfsone, ich denke, diese Funktion wird derzeit überprüft und kann daher interpretiert werden. Sie können auf diesen offenen Abschnitt verweisen , den ich in Warum erlaubt der C ++ - Standard keine teilweise Spezialisierung von Funktionsvorlagen?
Iammilind
43

Da eine teilweise Spezialisierung nicht zulässig ist - wie andere Antworten zeigten -, können Sie sie mit std::is_sameund std::enable_ifwie folgt umgehen:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}

Ausgabe:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

Bearbeiten : Falls Sie in der Lage sein müssen, alle anderen verbleibenden Fälle zu behandeln, können Sie eine Definition hinzufügen, die besagt, dass bereits behandelte Fälle nicht übereinstimmen sollten - andernfalls würden Sie in mehrdeutige Definitionen geraten. Die Definition könnte sein:

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}

Welches produziert:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

Obwohl diese All-Case- Sache etwas langweilig aussieht, ist es durchaus machbar, bis zu 5 oder ein paar weitere Spezialisierungen zu behandeln, da Sie dem Compiler alles mitteilen müssen, was Sie bereits getan haben.

Rubens
quelle
Es besteht wirklich keine Notwendigkeit, dies zu tun, da dies durch Überladen von Funktionen auf viel einfachere und klarere Weise behandelt werden kann.
Adrian
2
@Adrian Ich kann mir wirklich keinen anderen Ansatz zur Überladung von Funktionen vorstellen, um dies zu lösen. Sie haben festgestellt, dass eine teilweise Überlastung nicht zulässig ist, oder? Teilen Sie uns Ihre Lösung mit, wenn Sie der Meinung sind, dass sie klarer ist.
Rubens
1
Gibt es eine andere Möglichkeit, alle Vorlagenfunktionen einfach abzufangen?
Nick
15

Was ist Spezialisierung?

Wenn Sie Vorlagen wirklich verstehen möchten, sollten Sie sich funktionale Sprachen ansehen. Die Welt der Vorlagen in C ++ ist eine rein funktionale Subsprache für sich.

In funktionalen Sprachen erfolgt die Auswahl mithilfe von Pattern Matching :

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

Wie Sie sehen können, überladen wir die Definition von isJust.

Nun, C ++ - Klassenvorlagen funktionieren genauso. Sie stellen eine Haupterklärung, dass die Anzahl und die Art des Parameters erklärt. Es kann nur eine Deklaration sein oder auch als Definition dienen (Ihre Wahl), und dann können Sie (wenn Sie dies wünschen) Spezialisierungen des Musters bereitstellen und ihnen eine andere (sonst alberne) Version der Klasse zuordnen .

Bei Vorlagenfunktionen ist die Spezialisierung etwas umständlicher: Sie steht in Konflikt mit der Überlastungsauflösung. Daher wurde entschieden, dass sich eine Spezialisierung auf eine nicht spezialisierte Version bezieht und Spezialisierungen bei der Überlastungsauflösung nicht berücksichtigt werden. Daher lautet der Algorithmus zur Auswahl der richtigen Funktion:

  1. Führen Sie eine Überlastungsauflösung zwischen regulären Funktionen und nicht spezialisierten Vorlagen durch
  2. Wenn eine nicht spezialisierte Vorlage ausgewählt ist, prüfen Sie, ob für sie eine Spezialisierung vorhanden ist, die besser zu Ihnen passt

(Für eine eingehende Behandlung siehe GotW # 49 )

Daher ist die Vorlagenspezialisierung von Funktionen (im wahrsten Sinne des Wortes) ein Bürger der zweiten Zone. Meiner Meinung nach wären wir ohne sie besser dran: Ich habe noch keinen Fall gefunden, in dem eine Verwendung der Vorlagenspezialisierung nicht durch Überladung gelöst werden konnte.

Ist das eine Vorlagenspezialisierung?

Nein, es ist einfach eine Überlastung, und das ist in Ordnung. Tatsächlich funktionieren Überladungen normalerweise so, wie wir es erwarten, während Spezialisierungen überraschend sein können (denken Sie an den GotW-Artikel, den ich verlinkt habe).

Matthieu M.
quelle
"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead."Wie wäre es mit nicht typisierten Vorlagenparametern?
Jules GM
@ Julius: Sie können weiterhin Überladung verwenden, obwohl Sie einen Dummy-Parameter wie z boost::mpl::integral_c<unsigned, 3u>. Eine andere Lösung könnte auch sein, enable_if/ zu verwenden disable_if, obwohl es eine andere Geschichte ist.
Matthieu M.
7

Eine nicht klassifizierte, nicht variable Teilspezialisierung ist nicht zulässig, aber wie gesagt:

Alle Probleme in der Informatik können durch eine andere Indirektionsebene gelöst werden. - David Wheeler

Das Hinzufügen einer Klasse zum Weiterleiten des Funktionsaufrufs kann dieses Problem lösen. Hier ein Beispiel:

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");
user2709407
quelle
4

Nein. Sie können sich beispielsweise legal spezialisieren std::swap, aber Ihre eigene Überlastung nicht legal definieren. Das bedeutet, dass Sie nicht std::swapfür Ihre eigene benutzerdefinierte Klassenvorlage arbeiten können.

Überladung und teilweise Spezialisierung können in einigen Fällen den gleichen Effekt haben, sind aber bei weitem nicht alles.

Hündchen
quelle
4
Deshalb setzen Sie Ihre swapÜberladung in Ihren Namespace.
Jpalecek
2

Späte Antwort, aber einige späte Leser finden es möglicherweise nützlich: Manchmal kann auch eine Hilfsfunktion, die so konzipiert ist, dass sie spezialisiert werden kann, das Problem lösen.

Stellen wir uns also vor, wir haben versucht , Folgendes zu lösen:

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

OK, teilweise Spezialisierung auf Vorlagenfunktionen, das können wir nicht ... Also "exportieren" wir den für die Spezialisierung benötigten Teil in eine Hilfsfunktion, spezialisieren diesen und verwenden ihn:

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

Dies kann interessant sein , vor allem , wenn die Alternativen (normal Überlastungen statt Spezialisierungen, die Abhilfe durch Rubens vorgeschlagen, ... - nicht , dass diese schlecht sind oder meine ist besser, nur ein anderer ein) eine ganze Reihe von gemeinsamen Code teilen würde.

Aconcagua
quelle