Konvertierung von Lambda in nicht skalaren Typ angefordert

Dec 08 2020

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 Tund 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

4 Slava Dec 08 2020 at 00:22

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_ifoder 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) )) {}

4 NathanOliver Dec 08 2020 at 00:17

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::functionin 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.