Warum erhalte ich auch mit einfachem C ++ - Code komplexe Fehler?
Ich habe vor kurzem angefangen, mit C ++ zu spielen. Eine der Schwierigkeiten, die ich oft habe, ist, wenn der Compiler feststellt, dass es ein Problem mit den Typen gibt: Meistens sehen diese Compilerfehler für mich äußerst kryptisch aus. Hier ist ein Beispiel:
Smooth.h: 63: 15: Fehler: Ungültige benutzerdefinierte Konvertierung von 'Smooth <T, S> :: sum (std :: size_t) [mit T = short unsigned int; long unsigned int S = 20; std :: size_t = langes vorzeichenloses int] :: <lambda (kurzes vorzeichenloses int, kurzes vorzeichenloses int)> 'bis' kurzes vorzeichenloses int (*) (kurzes vorzeichenloses int, kurzes vorzeichenloses int)
Als ich mit C # gearbeitet habe, erinnere ich mich, dass es eine Möglichkeit gab, durch das Kombinieren von Generika ziemlich komplizierte Fehler zu erhalten, dh Generika als Parameter anderer Generika zu verwenden, möglicherweise auf mehr als zwei Ebenen, aber dafür musste man wirklich WTF-Code schreiben, wie z ::
Action<IEnumerable<Tuple<int?, Func<int?, ICollection<int>, bool>>>
In C ++ habe ich jedoch diese kryptischen Fehler mit Code, die ich selbst als ziemlich einfach betrachten würde. Was passiert also?
- Ist es eine Fähigkeit, die vom Compiler angegebenen Fehler zu verstehen?
- Oder sollte ich diese komplizierten Fehler überhaupt nicht bekommen und ihre Anwesenheit zeigt an, dass ich etwas falsch mache?
Wie in den Kommentaren angefordert, ist hier der entsprechende Quellcode, zumindest der relevante Teil. Kontext: Der Code soll auf einem Mikrocontroller ausgeführt werden, daher ist STL nicht verfügbar, sodass das Rad neu erfunden werden muss.
template <typename T, size_t S>
class Smooth {
T sum(size_t of = S) {
return this->reduce(
T(),
[](T acc, T value) { return acc + value; },
0,
of);
}
T reduce(
const T initial,
T (*acc)(T, T),
const size_t skip = 0,
const size_t take = S) {
...
}
};
Der eigentliche Fehler lag innerhalb return acc + value;und tritt auf, wenn Tes sich nicht um eine Ganzzahl handelt, sondern beispielsweise uint16_t. In diesem Fall ist das Lambda-Ergebnis eine ganze Zahl, während dies Terwartet wird. Das Update besteht darin, das Lambda durch zu ersetzen return static_cast<T>(acc + value);.
Antworten
Ist es eine Fähigkeit, die vom Compiler angegebenen Fehler zu verstehen?
Im Allgemeinen ja. Es sind viele Informationen enthalten, die Sie mit etwas Übung in etwas besser Lesbares extrahieren können.
-
Smooth.h: 63: 15: Fehler:
Sie haben einen Fehler in Zeile 63, Spalte 15. Ich denke, dies ist nur der Anfang des Lambda, aber jeder anständige Editor oder jede anständige IDE wird es Ihnen zeigen.
-
ungültige benutzerdefinierte Konvertierung von
Es versucht etwas zu konvertieren. Wir haben keine offensichtlichen Konvertierungen explizit im Code, daher ist dies wahrscheinlich die Konvertierung von zustandslosem Lambda in Funktionszeiger
-
Smooth<T, S>::sum(std::size_t) [with T = short unsigned int; long unsigned int S = 20; std::size_t = long unsigned int]::<lambda(short unsigned int, short unsigned int)>Dies ist der schwer zu lesende Teil IMO. Da der Lambda-Typ für die Funktion lokal ist, ruft er ihn auf:
Smooth<T, S>::sum(std::size_t)::<lambda(short unsigned int, short unsigned int)>Dies hängt jedoch von den äußeren Vorlagenparametern ab, sodass diese in der Mitte eingefügt werden:
[with T = short unsigned int; long unsigned int S = 20; std::size_t = long unsigned int]das macht es viel schwieriger zu lesen. Nur dieses Bit in eine separate Zeile zu setzen, würde helfen, und andere Compiler tun dies. Zum Vergleich: Der Fehler von GCC (auf Trunk, zum Zeitpunkt des Schreibens: Link ) ist sehr ausführlich, aber über mehrere Zeilen hinweg gut formatiert
-
zu
short unsigned int (*)(short unsigned int, short unsigned int)Welches ist der Typ des zweiten Parameters zu
reduce.
Sie machen jedoch etwas Komplexes - das Mischen von Funktionszeigern und Lambdas ist der einzige Grund, warum die Konvertierung erforderlich ist.
Ich denke in diesem Zusammenhang wäre es normaler reduce, eine Vorlage für einen Funktortyp zu haben, sodass ein Lambda ohne Konvertierung oder ein Funktionszeiger oder ein std::function. Auf diese Weise erhalten Sie möglicherweise eine Warnung oder einen Fehler in "Reduzieren", wenn Tund der Rückgabetyp des Funktors nicht kompatibel sind, es handelt sich jedoch um diese spezifischen Typen.
Wenn Sie beispielsweise Ihre Methode modellieren std::reduce, sieht sie folgendermaßen aus
template <class BinaryOp>
T reduce(
const T initial,
BinaryOp acc,
const size_t skip = 0,
const size_t take = S) {
// ...
}
(Ich weiß, dass Sie gesagt haben, Sie können die Standardbibliothek nicht verwenden, aber Sie können trotzdem sehen, wie sie ähnliche Probleme angeht.)
Die Hauptursache für diese Art von Fehlermeldungen ist, dass alle C ++ - Vorlagen angewendet werden, bevor Sie die reguläre Kompilierung durchführen, während in den meisten anderen Sprachen, die dies unterstützen, ein parametrischer Polymorphismus in das Haupttypsystem integriert ist. Leider können Fehler, die diese Grenze überschreiten, infamös kryptisch sein. Sie werden jedoch besser. In weiteren 35 Jahren könnte es tatsächlich lesbar sein.
Es beschwert sich, weil es ein Lambda-Objekt nicht auf einen Zeiger auf eine Funktion umwandeln kann.
Sie müssen die Funktion als einfachen Typ erfassen (z. B. X) und sie dann an eine andere Metafunktion weiterleiten, um zu entscheiden, ob sie aufrufbar ist.
template <typename X, typename enable_if = void>
struct CallAble
{};
template <typename X>
struct CallAble
< X
, std::void_t<decltype(std::declval<X>()(std::declval<T>(), std::declval<T>())>
>
{
typedef decltype(std::declval<X>()(std::declval<T>(), std::declval<T>()) type;
};
Gibt einen Typ nur zurück, wenn er aufrufbar ist, und dieser Typ ist der Ergebnistyp.
Sie müssen dies für Ihre spezifische Arbeit gestalten.
Ich habe den Fehler durch Lesen herausgefunden:
Smooth.h: 63: 15: Fehler: Ungültige benutzerdefinierte Konvertierung von '...' nach '...'
Eigentlich ziemlich klar. Etwas hat nicht gewirkt. Was versucht es zu bekommen?
short unsigned int (*) (short unsigned int, short unsigned int)
(*) ist der wichtige Teil. Zeiger auf eine Funktion.
Smooth <T, S> :: sum (std :: size_t) [mit T = short int ohne Vorzeichen; long unsigned int S = 20; std :: size_t = long unsigned int] :: <lambda (short unsigned int, short unsigned int)>
Okay, hässlich, was ist der eigentliche Weg?
Glatt <T, S> :: sum (std :: size_t) :: <lambda (kurzes vorzeichenloses int, kurzes vorzeichenloses int)>
Richtig also ein Lambda im Namespace / Funktion Smooth <T, S> :: sum (std :: size_t).
Ja, Lambdas werden nicht in Funktionszeiger umgewandelt.
Hilft mit einem Texteditor, in dem die Fehlerinformationen aufgeschnitten werden.