Conversión de lambda a tipo no escalar solicitada
Creé esta clase para poder tener un valor de cualquier tipo que sea fijo o recalculado cada vez que se usa:
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(); }
};
Todas las siguientes expresiones parecen funcionar bien:
Value<double> a = 2.2;
double b = 1.;
double c = a;
Value<double> d = &b;
Value<int> e = Value<int>([]() { return 1.; });
Pero cuando trato de hacer:
Value<double> f = []() { return 1.; };
se activa un error de compilación:
error: conversion from 'main()::<lambda()>' to non-scalar type 'Value<double>' requested
Puedes probar este ejemplo aquí .
¿Por qué la asignación funciona T
y no std::function<T()>
y cómo puedo hacer que funcione ?
Nota: Soy consciente de esta respuesta, pero no me quedó claro cómo solucionar el problema sin tener que llamar explícitamente al constructor como lo hice Value<double> e
.
Respuestas
¿Por qué la asignación funciona para T y no std :: function <T ()> y cómo puedo hacer que funcione?
Su código no usa asignación, pero copia la inicialización y
Además, la conversión implícita en la inicialización de la copia debe producir T directamente desde el inicializador, mientras que, por ejemplo, la inicialización directa espera una conversión implícita del inicializador a un argumento del constructor de T.
Entonces, para que funcione, debe hacer que su ctor acepte lambda directamente (este es un ejemplo simplificado):
template<typename T>
class Value {
std::function<T()> get;
public:
template<class Y>
Value(Y lambda ) : get( std::move( lambda ) ) {}
};
código en vivo Probablemente desee agregar restricción usando std::enable_if
o concepto si C ++ 20 está permitido a este ctor, así como en esta forma este constructor intentaría aceptar todo lo que otras sobrecargas no lo harían y pueden producir errores crípticos. Y de acuerdo con esta plantilla enable_if, el parámetro es lambda (con una firma particular) , podría ser tan simple como
template<class Y, typename = decltype(std::declval<Y&>()())>
Value(Y lambda ) : get( std::move( lambda ) ) {}
que admite C ++ 14. Aquí hay otro ejemplo en vivo donde puede ver que este constructor no se usa para el inicializador de tipo int
:
Value<double> d2 = 123;
prog.cpp: 9: 5: nota: plantilla candidata ignorada: falla de sustitución [con Y = int]: el tipo de objeto llamado 'int' no es una función o puntero de función Valor (Y lambda): get (std :: move (lambda )) {}
Una lambda no es una std::function
. Eso significa que cuando lo hagas
Value<double> f = []() { return 1.; };
debe convertir []() { return 1.; }
en a std::function
, que es una conversión definida por el usuario, y luego debe convertir eso std::function
en a Value<double>
, que es otra conversión definida por el usuario. Son dos conversiones definidas por el usuario cuando solo se le permite hasta una conversión de este tipo. Es por eso que el código no se puede compilar.