2017-09-20 2 views
0

Auch wenn es scheint, der Aufruf an fold_left im folgenden Programm ist spezifisch genug, um das Argument der Vorlage Funktion abzuleiten, tritt ein Kompilierungsfehler aufgrund einer template argument deduction Fehler.Vorlage Argument Abzug Fehler

#include <valarray> 
#include <functional> 

template<class Input, class Output> 
Output fold_left(Output zero, std::function<Output(Output,Input)> op, std::valarray<Input> data) 
{ 
    for (auto const& item : data) { 
     zero = op(zero, item); 
    } 
    return zero; 
} 

#include <iostream> 
int main() 
{ 
    std::valarray<int> data { 1, 2, 3, 4, 5 }; 
    std::cout << fold_left(0, std::plus<int>{}, data) << "\n"; 
} 
main.cpp: In function 'int main()': 
main.cpp:17:66: error: no matching function for call to 'fold_left(int, std::plus<int>, std::valarray<int>&)' 
    std::cout << fold_left/*<int,int>*/(0, std::plus<int>{}, data) << "\n"; 
                   ^
main.cpp:5:8: note: candidate: template<class Input, class Output> Output fold_left(Output, std::function<Output(Output, Input)>, std::valarray<_Tp>) 
Output fold_left(Output zero, std::function<Output(Output,Input)> op, std::valarray<Input> data) 
     ^~~~~~~~~ 
main.cpp:5:8: note: template argument deduction/substitution failed: 
main.cpp:17:66: note: 'std::plus<int>' is not derived from 'std::function<Output(Output, Input)>' 
    std::cout << fold_left/*<int,int>*/(0, std::plus<int>{}, data) << "\n"; 
                   ^

Das Minimalprogramm würde nur kompilieren, wenn fold_left mit aufgerufen:

fold_left<int, int>(0, std::plus<int>{}, data); 

Das fühlt sich für mich seltsam, da die Output Template-Argument offensichtlich sein sollte, da wir eine bereitgestellt int als erstes Argument (zero); In ähnlicher Weise sollte das Template-Argument Input korrekt abgeleitet werden, da wir ein std::valarray<int> als drittes Argument (data) bereitgestellt haben, das eine std::valarray<Input> erwartet. Die Reihenfolge der Argumente in der Definition von fold_leftare not relevant.

Warum scheitert die Vorlage Argument Ableitung hier?

+0

'std :: function' ist Schwergewicht dafür. Sie brauchen die Art der Löschung nicht. – chris

+0

@chris du hast Recht, aber es hilft nicht;) – YSC

Antwort

1

Das Problem ist, dass std::function<Output(Output,Input)> seine Vorlage Argumente nicht ordnungsgemäß ableiten kann, weil std::plus<int> ist kein std::function.

Typ-Abzug funktioniert nur, wenn die Vorlagentypen mit den übergebenen Argumenten übereinstimmen können; Es führt keine Transformationen durch, die durch Conversions/Konstruktionen durchgeführt werden. sondern nimmt den Typus in Totalität.

In diesem Fall std::plus<int> ist nicht ein std::function<int(int,int)>, es Cabrio zu einem std::function<int(int,int)> ist. Dies ist der Grund dafür, dass der Abzug fehlschlägt.

Die Alternativen sind entweder Beiträge zum Typ-Abzug mit einem Alias ​​zu brechen, explizit einen std::function Typen passieren, oder die Funktion als andere Vorlage Argument zu akzeptieren

(die eigentlich effizienter sein können) 1. Brechen Beitrag zum Typ-Abzug mit einem Alias ​​

Wenn Sie einen Aliasnamen des Typs machen, so dass die Typen zum Abzug nicht tragen, dann kann es das erste und das dritte Argument lassen die bestimmen Typen der std::function

(aus meinem Kopf; ungetestet)

template<typename T> 
struct nodeduce{ using type = T; }; 

template<typename T> 
using nodeduce_t = typename nodeduce<T>::type; 

template<class Input, class Output> 
Output fold_left(Output zero, std::function<nodeduce_t<Output>(nodeduce_t<Output>,nodeduce_t<Input>)> op, std::valarray<Input> data) 
{ 
    .... 
} 

Es ist ein bisschen von einem hässlichen Ansatz, aber es wird std::function zum Abzug von dem Beitrag deaktivieren, da die Typen durch nodeduce_t umgewandelt werden.

2. Pass als std::function

Dies ist der einfachste Ansatz; Ihre Telefonvorwahl würde:

fold_left(0, std::function<int(int,int)>(std::plus<int>{}), data); 

Zugegeben, es ist nicht schön - aber es würde den Abzug lösen, weil std::function<int(int,int)> direkt die Eingabe entsprechen können.

3. Pass als Template-Argument

Das tatsächlich hat einige zusätzliche Leistungsvorteile; wenn Sie das Argument als Vorlagentyp akzeptieren, dann auch erlaubt es Ihre Funktion vom Compiler inlined besser werden (etwas, das std::function Probleme mit hat)

template<class Input, typename Fn, class Output> 
Output fold_left(Output zero, Fn&& op, std::valarray<Input> data) 
{ 
    for (auto const& item : data) { 
     zero = op(zero, item); 
    } 
    return zero; 
} 

All dies sind mögliche Alternative, die die gleiche Aufgabe erfüllen würde . Es ist auch erwähnenswert, dass Compiler-Validierung Erwähnen mit SFINAE mit Dingen getan werden kann, wie enable_if oder decltype Bewertungen:

template<class Input, typename Fn, class Output, typename = decltype(std::declval<Output> = std::declval<Fn>(std::declval<Output>, std::declval<Input>), void())> 
Output fold_left(...){ ... } 

Die decltype(...) Syntax stellt der Ausdruck wohlgeformt ist, und wenn ja, dann kann es diese Funktion für die Überladungsauflösung.

bearbeiten: Aktualisiert die decltype Auswertung Output und Input Typen zu testen. Mit C++ 17 können Sie auch std::is_convertible_v<std::invoke_result_t<F&, Output, Input>,Output> in einer enable_if verwenden, wie von Barry in einer anderen Antwort vorgeschlagen.

4

Da std::plus<int> keine Instanz von std::function<O(O,I)> für einige Arten O,I und sie sind dort in einem abgeleiteten Kontext.

Die Lösung ist std::function<O(O,I)> mit einem neuen Template-Parameter zu ersetzen, die sich gezwungen ist, nur auf der Grundlage der Anforderung, dass es aufrufbare mit zwei Argumenten ist O,I und dass der Rückgabetyp ist umwandelbar in O:

template<class Input, class Output, class F, 
    std::enable_if_t<std::is_convertible_v< 
     std::invoke_result_t<F&, Output, Input>, 
     Output>, int> = 0> 
Output fold_left(Output zero, F op, std::valarray<Input> data) 
{ ... } 

Sie könnte separat das Argument op in einem nicht-abgeleiteten Kontext umhüllen, aber Sie brauchen nicht den Typ löschen, den std::function bietet, so dass es unnötig ist.

1

Das fühlt sich für mich seltsam, da das Output Template-Argument offensichtlich sein sollte, da wir

Der Compiler wird versuchen, ein int als erstes Argument bereitgestellt Output in jedem Ort zu lösen, ist es verwendet wird. Wenn die abgeleiteten Typen aus verschiedenen Parametern nicht übereinstimmen, erhalten Sie eine Mehrdeutigkeit, die dazu führt, dass die Ableitung fehlschlägt.

Da Sie verwenden Output in Output zero und std::function<Output(Output,Input)> op der Compiler 0 und std::plus<int> verwenden wird, um zu versuchen und herauszufinden, was Output ist. ist keine std :: function, daher kann der Typ nicht ermittelt werden und deshalb schlägt die Substitution fehl.

Wenn Sie werfen std::plus<int>{} in einen std::function dann kann der Compiler die Template-Typen

std::cout << fold_left(0, std::function<int(int, int)>(std::plus<int>{}), data) << "\n"; 

ableiten Wenn Sie das nicht haben wollen, zu tun, obwohl Sie müssen stattdessen die Funktion ein generischer Typ machen und Verwenden Sie dann SFINAE, um die Vorlage wie answer, die von Barry bereitgestellt wird, zu beschränken.

Verwandte Themen