Conversion de type lambda en type non scalaire demandée

Dec 08 2020

J'ai créé cette classe pour pouvoir avoir une valeur de n'importe quel type qui est soit fixe, soit recalculée à chaque fois qu'elle est utilisée:

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(); }
};

Toutes les expressions suivantes semblent fonctionner correctement:

Value<double> a = 2.2;
double b = 1.;
double c = a;
Value<double> d = &b;
Value<int> e = Value<int>([]() { return 1.; });

Mais quand j'essaye de faire:

Value<double> f = []() { return 1.; };

une erreur de compilation est déclenchée:

error: conversion from 'main()::<lambda()>' to non-scalar type 'Value<double>' requested

Vous pouvez essayer cet exemple ici .

Pourquoi l'attribution fonctionne-t-elle pour Tet non std::function<T()>et comment puis-je y arriver?

Remarque: je suis au courant de cette réponse, mais je ne suis pas devenu clair pour moi comment résoudre le problème sans avoir à appeler explicitement le constructeur comme je l'ai fait pour Value<double> e.

Réponses

4 Slava Dec 08 2020 at 00:22

Pourquoi l'assignation fonctionne-t-elle pour T et non pour std :: function <T ()> et comment puis-je faire en sorte que ce soit le cas?

Votre code n'utilise pas d'attribution, mais copie l'initialisation et

De plus, la conversion implicite dans l'initialisation de copie doit produire T directement à partir de l'initialiseur, alors que, par exemple, l'initialisation directe attend une conversion implicite de l'initialiseur en un argument du constructeur de T.

Donc, pour que cela fonctionne, vous devez faire en sorte que votre ctor accepte directement lambda (ceci est un exemple simplifié):

template<typename T>
class Value {
    std::function<T()> get;    
public:
    
    template<class Y>
    Value(Y lambda ) : get( std::move( lambda ) )  {}
};

live code Vous voudrez probablement ajouter une restriction en utilisant std::enable_ifou concept si C ++ 20 est autorisé à ce ctor ainsi que sous cette forme ce constructeur essaierait d'accepter toutes les autres surcharges ne le feraient pas et pourraient produire des erreurs cryptiques. Et selon ce paramètre de modèle enable_if est lambda (avec une signature particulière), cela pourrait être aussi simple que

template<class Y, typename = decltype(std::declval<Y&>()())>
Value(Y lambda ) : get( std::move( lambda ) )  {}

qui prend en charge C ++ 14. Voici encore un autre exemple en direct où vous pouvez voir que ce constructeur n'est pas utilisé pour l'initialiseur de type int:

 Value<double> d2 = 123;

prog.cpp: 9: 5: note: modèle candidat ignoré: échec de la substitution [avec Y = int]: le type d'objet appelé 'int' n'est pas une fonction ou un pointeur de fonction Valeur (Y lambda): get (std :: move (lambda )) {}

4 NathanOliver Dec 08 2020 at 00:17

Un lambda n'est pas un std::function. Cela veut dire quand tu fais

Value<double> f = []() { return 1.; };

vous devez convertir []() { return 1.; }en a std::function, qui est une conversion définie par l'utilisateur, puis vous devez le convertir std::functionen a Value<double>, qui est une autre conversion définie par l'utilisateur. Il s'agit de deux conversions définies par l'utilisateur lorsque vous n'êtes autorisé qu'à une seule de ces conversions. C'est pourquoi le code ne parvient pas à se compiler.