Konvertierung von Lambda in nicht skalaren Typ angefordert
Ich habe diese Klasse erstellt, damit ich einen Wert eines beliebigen Typs haben kann, der bei jeder Verwendung entweder festgelegt oder neu berechnet wird:
template<typename T>
class Value {
private:
bool fixed;
union {
T value;
std::function<T()> get;
};
public:
Value(const T& value) : fixed(true), value(value) {}
Value(const std::function<T()>& get) : fixed(false), get(get) {}
Value(const T *pointer) : Value([pointer]() { return *pointer; }) {}
~Value() {}
operator T() { return fixed ? value : get(); }
};
Alle folgenden Ausdrücke scheinen gut zu funktionieren:
Value<double> a = 2.2;
double b = 1.;
double c = a;
Value<double> d = &b;
Value<int> e = Value<int>([]() { return 1.; });
Aber wenn ich es versuche:
Value<double> f = []() { return 1.; };
Ein Kompilierungsfehler wird ausgelöst:
error: conversion from 'main()::<lambda()>' to non-scalar type 'Value<double>' requested
Sie können dieses Beispiel hier ausprobieren .
Warum funktioniert das Zuweisen für T
und nicht std::function<T()>
und wie kann ich es so machen, dass es funktioniert?
Hinweis: Diese Antwort ist mir bekannt , aber mir ist nicht klar geworden, wie ich das Problem beheben kann, ohne den Konstruktor explizit aufrufen zu müssen, wie ich es getan habe Value<double> e
.
Antworten
Warum funktioniert das Zuweisen für T und nicht für std :: function <T ()> und wie kann ich es so machen, dass es funktioniert?
Ihr Code verwendet keine Zuweisung, sondern kopiert die Initialisierung und
Außerdem muss die implizite Konvertierung bei der Kopierinitialisierung T direkt vom Initialisierer erzeugen, während beispielsweise die direkte Initialisierung eine implizite Konvertierung vom Initialisierer in ein Argument des Konstruktors von T erwartet.
Damit es funktioniert, müssen Sie Ihren Ctor dazu bringen, Lambda direkt zu akzeptieren (dies ist ein vereinfachtes Beispiel):
template<typename T>
class Value {
std::function<T()> get;
public:
template<class Y>
Value(Y lambda ) : get( std::move( lambda ) ) {}
};
Live-Code Sie möchten wahrscheinlich eine Einschränkung mithilfe von std::enable_if
oder ein Konzept hinzufügen, wenn C ++ 20 für diesen Ctor zulässig ist. In dieser Form würde dieser Konstruktor versuchen, alles zu akzeptieren, was andere Überladungen nicht und möglicherweise kryptische Fehler erzeugen. Und gemäß dieser enable_if-Vorlage ist param lambda (mit bestimmter Signatur), es könnte so einfach sein wie
template<class Y, typename = decltype(std::declval<Y&>()())>
Value(Y lambda ) : get( std::move( lambda ) ) {}
welches C ++ 14 unterstützt. Hier ist ein weiteres Live-Beispiel, in dem Sie sehen können, dass dieser Konstruktor nicht für den Initialisierer vom Typ verwendet wird int
:
Value<double> d2 = 123;
prog.cpp: 9: 5: Hinweis: Kandidatenvorlage ignoriert: Substitutionsfehler [mit Y = int]: Der aufgerufene Objekttyp 'int' ist keine Funktion oder Funktionszeiger. Wert (Y lambda): get (std :: move (lambda) )) {}
Ein Lambda ist kein std::function
. Das heißt, wenn Sie es tun
Value<double> f = []() { return 1.; };
Sie müssen konvertieren []() { return 1.; }
in ein std::function
, die eine benutzerdefinierte Konvertierung ist, und dann müssen Sie das konvertieren std::function
in ein Value<double>
, die ein anderer Benutzer definierte Umwandlung ist. Das sind zwei benutzerdefinierte Konvertierungen, wenn Sie immer nur bis zu einer solchen Konvertierung zulassen. Aus diesem Grund kann der Code nicht kompiliert werden.