Richiesta conversione da lambda a tipo non scalare
Ho creato questa classe in modo da poter avere un valore di qualsiasi tipo che viene fisso o ricalcolato ogni volta che viene utilizzato:
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(); }
};
Tutte le seguenti espressioni sembrano funzionare bene:
Value<double> a = 2.2;
double b = 1.;
double c = a;
Value<double> d = &b;
Value<int> e = Value<int>([]() { return 1.; });
Ma quando provo a fare:
Value<double> f = []() { return 1.; };
viene attivato un errore di compilazione:
error: conversion from 'main()::<lambda()>' to non-scalar type 'Value<double>' requested
Puoi provare questo esempio qui .
Perché l'assegnazione funziona per Te non std::function<T()>e come posso fare in modo che funzioni?
Nota: sono a conoscenza di questa risposta ma non mi è stato chiaro come risolvere il problema senza dover chiamare esplicitamente il costruttore come ho fatto per Value<double> e.
Risposte
Perché l'assegnazione funziona per T e non per std :: function <T ()> e come posso farlo in modo che funzioni?
Il codice non utilizza l'assegnazione, ma copia l'inizializzazione e
Inoltre, la conversione implicita nell'inizializzazione della copia deve produrre T direttamente dall'inizializzatore, mentre, ad esempio, l'inizializzazione diretta si aspetta una conversione implicita dall'inizializzatore a un argomento del costruttore di T.
Quindi per farlo funzionare devi fare in modo che il tuo ctor accetti lambda direttamente (questo è un esempio semplificato):
template<typename T>
class Value {
std::function<T()> get;
public:
template<class Y>
Value(Y lambda ) : get( std::move( lambda ) ) {}
};
codice live Probabilmente vorrai aggiungere la restrizione using std::enable_ifo concept se C ++ 20 è consentito a questo ctor così come in questa forma questo costruttore tenterebbe di accettare qualsiasi altro sovraccarico non lo farebbe e potrebbe produrre errori criptici. E secondo questo parametro del modello enable_if è lambda (con una firma particolare) potrebbe essere semplice come
template<class Y, typename = decltype(std::declval<Y&>()())>
Value(Y lambda ) : get( std::move( lambda ) ) {}
che supporta C ++ 14. Ecco ancora un altro esempio dal vivo in cui puoi vedere che questo costruttore non è utilizzato per l'inizializzatore di tipo int:
Value<double> d2 = 123;
prog.cpp: 9: 5: nota: modello candidato ignorato: sostituzione fallita [con Y = int]: il tipo di oggetto chiamato 'int' non è una funzione o un puntatore a funzione Valore (Y lambda): get (std :: move (lambda )) {}
Un lambda non è un file std::function. Ciò significa che quando lo fai
Value<double> f = []() { return 1.; };
è necessario convertire []() { return 1.; }in a std::function, che è una conversione definita dall'utente, quindi è necessario convertirla std::functionin a Value<double>, che è un'altra conversione definita dall'utente. Si tratta di due conversioni definite dall'utente quando è consentita solo una di queste conversioni. Questo è il motivo per cui il codice non viene compilato.