Richiesta conversione da lambda a tipo non scalare

Dec 08 2020

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

4 Slava Dec 08 2020 at 00:22

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

4 NathanOliver Dec 08 2020 at 00:17

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.