Conversión de lambda a tipo no escalar solicitada

Dec 08 2020

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

4 Slava Dec 08 2020 at 00:22

¿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_ifo 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 )) {}

4 NathanOliver Dec 08 2020 at 00:17

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