Запрошено преобразование из лямбда в нескалярный тип

Dec 08 2020

Я создал этот класс, чтобы иметь значение любого типа, которое либо фиксировано, либо пересчитывается каждый раз, когда оно используется:

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(); }
};

Кажется, все следующие выражения работают нормально:

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

Но когда я пытаюсь сделать:

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

срабатывает ошибка компиляции:

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

Вы можете попробовать этот пример здесь .

Почему назначение работает, Tа не работает std::function<T()>и как сделать так?

Примечание. Мне известен этот ответ, но мне неясно, как исправить проблему без явного вызова конструктора, как это сделал я для Value<double> e.

Ответы

4 Slava Dec 08 2020 at 00:22

Почему назначение работает для T, а не для std :: function <T ()> и как я могу это сделать?

Ваш код не использует присваивание, а копирует инициализацию и

Кроме того, неявное преобразование при копировании-инициализации должно производить T непосредственно из инициализатора, в то время как, например, прямая инициализация ожидает неявного преобразования из инициализатора в аргумент конструктора T.

Итак, чтобы заставить его работать, вы должны заставить ваш ctor принимать лямбда напрямую (это упрощенный пример):

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

live code Вы, вероятно, захотите добавить ограничение using std::enable_ifили концепцию, если C ++ 20 разрешен для этого ctor, а также в этой форме этот конструктор попытается принять, что другие перегрузки не будут и могут вызывать загадочные ошибки. И в соответствии с этим параметром шаблона enable_if является лямбда (с определенной сигнатурой), это может быть так же просто, как

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

который поддерживает C ++ 14. Вот еще один живой пример, где вы можете видеть, что этот конструктор не используется для инициализатора типа int:

 Value<double> d2 = 123;

prog.cpp: 9: 5: примечание: шаблон кандидата игнорируется: ошибка подстановки [с Y = int]: вызываемый тип объекта 'int' не является функцией или указателем функции Значение (Y лямбда): get (std :: move (lambda )) {}

4 NathanOliver Dec 08 2020 at 00:17

Лямбда - это не std::function. Это означает, что когда вы это сделаете

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

вам нужно преобразовать []() { return 1.; }в a std::function, определяемое пользователем преобразование, а затем вам необходимо преобразовать это std::functionв Value<double>другое преобразование, определяемое пользователем. Это два определяемых пользователем преобразования, когда вам разрешено только одно такое преобразование. Вот почему код не компилируется.