2015-06-15 5 views
7

Ich verwende SFINAE im Stil von this answer, um ein generisches Vektorobjekt unter Verwendung einer geeigneten Elementfunktion aufzurufen. Zum Beispiel ruft der folgende Code operator[](int) const zuerst, und wenn das nicht dann operator()(int) const existiert:C++ Warum scheitert SFINAE nur mit einem Klassenvorlagenparameter?

template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); }; 
template<> struct rank<0> {}; 

template<typename VectorType> 
struct VectorWrapper 
{ 
    auto get(int i) const 
    { 
     return get(v, i, rank<5>()); 
    } 

    template<typename V, typename = std::enable_if_t<has_bracket_operator<const V>::value> > 
    auto get(V const& v, int i, rank<2>) const 
    { 
     return v[i]; 
    } 

    template<typename V, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> > 
    auto get(V const& v, int i, rank<1>) const 
    { 
     return v(i); 
    } 

    VectorType v; 
}; 

Mit den has_bracket_operator und has_parenthesis_operator Zügen eingerichtet wie in this thread vorgeschlagen, kompiliert das Ganze und seems to work.

Das Übergeben des Member-Vektors an die überlasteten Klassenvorlagen scheint jedoch von Anfang an unnötig zu sein, also habe ich versucht, dasselbe einzurichten, ohne es zu übergeben. Dazu ersetzen ich den Template-Parameter V mit dem VectorType Parameter zur Einrichtung der Klassenvorlage verwendet:

template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> > 
    auto get(int i, rank<2>) const 
    { 
     return v[i]; 
    } 

    template<typename = std::enable_if_t<has_parenthesis_operator<VectorType>::value> > 
    auto get(int i, rank<1>) const 
    { 
     return v(i); 
    } 

Nun aber schlägt die Kompilierung (in gcc 5.1.0) mit der folgenden Fehlermeldung:

/usr/local/include/c++/5.1.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = has_parenthesis_operator<std::vector<int> >::value; _Tp = void]': 
main.cpp:46:10: required from 'struct VectorWrapper<std::vector<int> >' 
main.cpp:59:38: required from here 
/usr/local/include/c++/5.1.0/type_traits:2388:61: error: no type named 'type' in 'struct std::enable_if<false, void>' 
    using enable_if_t = typename enable_if<_Cond, _Tp>::type; 

DEMO

Fragen:

  • Was ist der Grund für diesen Kompilierungsfehler?
  • Gibt es eine andere Problemumgehung als die meines ersten Codeblocks? (Das heißt, einen, der den üblichen Kodierungsstil beibehält - wo man keine Mitglieder passieren muss).

Antwort

3

SFINAE kommt zu uns aus [temp.deduct]/8, Hervorhebung von mir:

Wenn eine Substitution zu einem ungültigen Typ oder einen Ausdruck, Typ Abzug versagt. Ein ungültiger Typ oder Ausdruck ist einer, der schlecht gebildet wäre, mit einer erforderlichen Diagnose, wenn er mit den ersetzten Argumenten geschrieben wird. [Hinweis: Wenn keine Diagnose erforderlich ist, ist das Programm immer noch schlecht formatiert. Die Zugriffsprüfung erfolgt im Rahmen der Substitution . -end note] Nur ungültige Typen und Ausdrücke im direkten Kontext des Funktionstyps und seine Vorlagenparametertypen können zu einem Fehlschlagen des Abzugs führen.

Der unmittelbare Kontext ist, was in der Vorlage Deklaration ist.In Ihrem ersten Beispiel:

template<typename V, typename = std::enable_if_t<has_bracket_operator<const V>::value> > 
auto get(V const& v, int i, rank<2>) const 

V ist in unmittelbarem Zusammenhang, so dass eine Substitution Fehler auf den enable_if ist nur ein Abzug Versagen.

jedoch in Ihrem zweiten Beispiel:

template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> > 
auto get(int i, rank<2>) const 

VectorType ist nicht im unmittelbaren Kontext von get, so dass hier ein Fehler wäre kein Abzug Ausfall sein, wäre es ein harter Fehler sein.

Es sei denn, VectorType passiert, alle diese Operatoren zu haben.

Die Lösung für jedes Vorlagenproblem besteht darin, einfach weitere Vorlagen hinzuzufügen. In diesem Fall Kraft VectorType im unmittelbaren Kontext sein, durch eine andere Art der Einführung:

template<typename T=VectorType, typename = std::enable_if_t<has_bracket_operator<T>::value> > 
auto get(int i, rank<2>) const 

Und get<>() nennt.

+0

Ich bin mir nicht sicher, ob der "unmittelbare Kontext" hier relevant ist (obwohl die Lösung vernünftig ist). Dies ist [CWG 1635] (http://wg21.link/cwg1635). –

+0

@ T.C. Bedeutet das, dass es korrekt ist, nicht zu kompilieren, aber möglicherweise nicht, oder dass es kompilieren sollte und die Compiler einen Fehler haben? – Barry

+0

Ich habe keine Ahnung, was CWG zu diesem Thema tun wird. Im Moment würde ich das wahrscheinlich unterspezifiziert nennen. –

4

In Ihrem fehlgeschlagenen Beispiel wurde der Vorlagenparameter VectorType bereits durch die Uhrzeit festgelegt, zu der die Auflösung erfolgt. Damit SFINAE funktioniert, müssen Sie bei diesem Methodenaufruf die Template-Parameter angeben, die Sie für SFINAE verwenden. Im Folgenden ist eine Modifikation des ersten Beispiels zu arbeiten, wie Sie wollen:

template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); }; 
template<> struct rank<0> {}; 

template<typename VectorType> 
struct VectorWrapper 
{ 
    auto get(int i) const 
    { 
     return get(v, i, rank<5>()); 
    } 

    template<typename V=VectorType, typename = std::enable_if_t<has_bracket_operator<const V>::value> > 
    auto get(int i, rank<2>) const 
    { 
     return v[i]; 
    } 

    template<typename V=VectorType, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> > 
    auto get(int i, rank<1>) const 
    { 
     return v(i); 
    } 

    VectorType v; 
}; 

Auf diese Weise V aufgelöst wird, wenn get genannt wird, und es wird richtig SFINAE verwenden.

+0

ich die Änderung vorgenommen hat. – JKor

3

Oder Sie können einfach verwenden tag-Dispatching:

auto get(int i) const 
{ 
    return get(i, has_bracket_operator<VectorType>(), has_parenthesis_operator<VectorType>()); 
} 

auto get(int i, std::true_type /*brackets*/, std::false_type /*parenthesis*/) const 
{ 
    return v[i]; 
} 

auto get(int i, std::false_type /*brackets*/, std::true_type /*parenthesis*/) const 
{ 
    return v(i); 
} 

demo

+1

Ich denke, das wird fehlschlagen, wenn der Vektor nicht 'operator() (int)' hat. – davidhigh

+0

warum denkst du so? http://coliru.stacked-crooked.com/a/0a32631cc21deeb3. Ich verstehe deine Bemerkung nicht. Vektor hat nicht 'operator() (int)' – grisha

+0

, weil ich gedacht hätte, dass der Compiler diese Funktionen instanziiert, unabhängig von den Parametern, die daran übergeben werden. Und wenn es versucht, die 'operator()' Version zu instanziieren, schlägt es fehl. Ich schätze hier Ausdruck SFINAE funktioniert: als 'decltype (v (i))' ist schlecht gebildet, die Funktion ist möglicherweise automatisch deaktiviert (?). – davidhigh