Conversion de type lambda en type non scalaire demandée
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 T
et 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
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_if
ou 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 )) {}
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::function
en 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.