Ansätze zur Funktion von SFINAE in C ++

40

Ich verwende die Funktion SFINAE stark in einem Projekt und bin mir nicht sicher, ob es Unterschiede zwischen den folgenden beiden Ansätzen gibt (außer Stil):

#include <cstdlib>
#include <type_traits>
#include <iostream>

template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo()
{
    std::cout << "method 1" << std::endl;
}

template <class T, std::enable_if_t<std::is_same_v<T, double>>* = 0>
void foo()
{
    std::cout << "method 2" << std::endl;
}

int main()
{
    foo<int>();
    foo<double>();

    std::cout << "Done...";
    std::getchar();

    return EXIT_SUCCESS;
}

Die Programmausgabe ist wie erwartet:

method 1
method 2
Done...

Ich habe gesehen, dass Methode 2 im Stapelüberlauf häufiger verwendet wird, aber ich bevorzuge Methode 1.

Gibt es Umstände, unter denen sich diese beiden Ansätze unterscheiden?

Keith
quelle
Wie führen Sie dieses Programm aus? Es wird nicht für mich kompiliert.
Alter Igel
@alter igel es wird ein C ++ 17 Compiler benötigt. Ich habe MSVC 2019 verwendet, um dieses Beispiel zu testen, aber ich arbeite hauptsächlich mit Clang.
Keith
Verwandte: Warum-sollte-ich-vermeidbare-wenn-in-Funktion-Signaturen vermeiden und C ++ 20 führt auch neue Wege mit Konzept :-)
Jarod42
@ Jarod42 Konzepte sind für mich eines der am meisten benötigten Dinge ab C ++ 20.
Val sagt Reinstate Monica

Antworten:

35

Ich habe gesehen, dass Methode 2 im Stapelüberlauf häufiger verwendet wird, aber ich bevorzuge Methode 1.

Vorschlag: Methode 2 bevorzugen.

Beide Methoden arbeiten mit einzelnen Funktionen. Das Problem tritt auf, wenn Sie mehr als eine Funktion mit derselben Signatur haben und nur eine Funktion des Satzes aktivieren möchten.

Angenommen, Sie aktivieren möchten foo(), Version 1, wenn bar<T>()(so tun , es ist eine constexprFunktion) ist true, und foo(), Version 2, wenn bar<T>()ist false.

Mit

template <typename T, typename = std::enable_if_t<true == bar<T>()>>
void foo () // version 1
 { }

template <typename T, typename = std::enable_if_t<false == bar<T>()>>
void foo () // version 2
 { }

Sie erhalten einen Kompilierungsfehler, weil Sie eine Mehrdeutigkeit haben: zwei foo()Funktionen mit derselben Signatur (ein Standardvorlagenparameter ändert die Signatur nicht).

Aber die folgende Lösung

template <typename T, std::enable_if_t<true == bar<T>(), bool> = true>
void foo () // version 1
 { }

template <typename T, std::enable_if_t<false == bar<T>(), bool> = true>
void foo () // version 2
 { }

funktioniert, weil SFINAE die Signatur der Funktionen ändert.

Unabhängige Beobachtung: Es gibt auch eine dritte Methode: Aktivieren / Deaktivieren des Rückgabetyps (außer natürlich Klassen- / Strukturkonstruktoren)

template <typename T>
std::enable_if_t<true == bar<T>()> foo () // version 1
 { }

template <typename T>
std::enable_if_t<false == bar<T>()> foo () // version 2
 { }

Als Methode 2 ist Methode 3 mit der Auswahl alternativer Funktionen mit derselben Signatur kompatibel.

max66
quelle
1
Vielen Dank für die tolle Erklärung, ich werde von nun an die Methoden 2 & 3 bevorzugen :-)
Keith
"Ein Standardvorlagenparameter ändert die Signatur nicht" - wie unterscheidet sich dies in Ihrer zweiten Variante, die auch Standardvorlagenparameter verwendet?
Eric
1
@Eric - Nicht einfach zu sagen ... Ich nehme an, die andere Antwort erklärt dies besser ... Wenn SFINAE das Standardvorlagenargument aktiviert / deaktiviert, foo()bleibt die Funktion verfügbar, wenn Sie sie mit einem expliziten zweiten Vorlagenparameter (dem foo<double, double>();Aufruf) aufrufen. Und wenn verfügbar, besteht eine Mehrdeutigkeit mit der anderen Version. Bei Methode 2 aktiviert / deaktiviert SFINAE das zweite Argument, nicht den Standardparameter. Sie können es also nicht als Erklärung des Parameters bezeichnen, da ein Substitutionsfehler vorliegt, der keinen zweiten Parameter zulässt. Die Version ist also nicht verfügbar, also keine Mehrdeutigkeit
max66
3
Methode 3 hat den zusätzlichen Vorteil, dass sie im Allgemeinen nicht in den Symbolnamen eindringt. Die Variante auto foo() -> std::enable_if_t<...>ist oft nützlich, um das Ausblenden der Funktionssignatur zu vermeiden und die Verwendung der Funktionsargumente zu ermöglichen.
Deduplikator
@ max66: Der entscheidende Punkt ist also, dass ein Substitutionsfehler in einem Vorlagenparameter-Standard kein Fehler ist, wenn der Parameter angegeben wird und kein Standard benötigt wird.
Eric
21

Neben der Antwort von max66 besteht ein weiterer Grund für die Bevorzugung von Methode 2 darin, dass Sie mit Methode 1 (versehentlich) einen expliziten Typparameter als zweites Vorlagenargument übergeben und den SFINAE-Mechanismus vollständig umgehen können. Dies kann als Tippfehler, Kopier- / Einfügefehler oder als Versehen in einem größeren Vorlagenmechanismus auftreten.

#include <cstdlib>
#include <type_traits>
#include <iostream>

// NOTE: foo should only accept T=int
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo(){
    std::cout << "method 1" << std::endl;
}

int main(){

    // works fine
    foo<int>();

    // ERROR: subsitution failure, as expected
    // foo<double>();

    // Oops! also works, even though T != int :(
    foo<double, double>();

    return 0;
}

Live-Demo hier

alter igel
quelle
Guter Punkt. Der Mechanismus kann entführt werden.
Max66