Conversão de lambda para tipo não escalar solicitada

Dec 08 2020

Eu criei esta classe para que eu pudesse ter um valor de qualquer tipo que seja fixo ou recalculado toda vez que for usado:

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 as seguintes expressões parecem funcionar bem:

Value<double> a = 2.2;
double b = 1.;
double c = a;
Value<double> d = &b;
Value<int> e = Value<int>([]() { return 1.; });

Mas quando tento fazer:

Value<double> f = []() { return 1.; };

um erro de compilação é acionado:

error: conversion from 'main()::<lambda()>' to non-scalar type 'Value<double>' requested

Você pode tentar este exemplo aqui .

Por que a atribuição funciona para Te não std::function<T()>e como posso fazer com que funcione ?

Observação: estou ciente dessa resposta, mas não fiquei claro para mim como corrigir o problema sem ter que chamar explicitamente o construtor como fiz para Value<double> e.

Respostas

4 Slava Dec 08 2020 at 00:22

Por que a atribuição funciona para T e não para std :: function <T ()> e como posso fazer com que funcione?

Seu código não usa atribuição, mas copia a inicialização e

Além disso, a conversão implícita na inicialização de cópia deve produzir T diretamente do inicializador, enquanto, por exemplo, a inicialização direta espera uma conversão implícita do inicializador para um argumento do construtor de T.

Então, para fazer funcionar, você precisa fazer seu ctor aceitar lambda diretamente (este é um exemplo simplificado):

template<typename T>
class Value {
    std::function<T()> get;    
public:
    
    template<class Y>
    Value(Y lambda ) : get( std::move( lambda ) )  {}
};

código ao vivo Você provavelmente deseja adicionar restrição usando std::enable_ifou conceito se C ++ 20 for permitido a este ctor, bem como nesta forma, este construtor tentaria aceitar que todas as outras sobrecargas não fariam e podem produzir erros crípticos. E de acordo com este parâmetro enable_if do modelo é lambda (com uma assinatura particular) , poderia ser tão simples quanto

template<class Y, typename = decltype(std::declval<Y&>()())>
Value(Y lambda ) : get( std::move( lambda ) )  {}

que suporta C ++ 14. Aqui está outro exemplo ao vivo onde você pode ver que este construtor não é usado para o inicializador do tipo int:

 Value<double> d2 = 123;

prog.cpp: 9: 5: nota: modelo candidato ignorado: falha de substituição [com Y = int]: o tipo de objeto chamado 'int' não é uma função ou ponteiro de função Valor (Y lambda): get (std :: move (lambda )) {}

4 NathanOliver Dec 08 2020 at 00:17

Um lambda não é um std::function. Isso significa quando você faz

Value<double> f = []() { return 1.; };

você precisa converter []() { return 1.; }em a std::function, que é uma conversão definida pelo usuário, e então você precisa convertê-la std::functionem a Value<double>, que é outra conversão definida pelo usuário. São duas conversões definidas pelo usuário, quando você só tem permissão para até uma dessas conversões. É por isso que o código falha ao compilar.