I created this class so I could have a value of any type which is either fixed or recalculated everytime it's used:
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(); }
};
All the following expressions seem to work fine:
Value<double> a = 2.2;
double b = 1.;
double c = a;
Value<double> d = &b;
Value<int> e = Value<int>([]() { return 1.; });
But when I try to do:
Value<double> f = []() { return 1.; };
a compilation error is triggered:
error: conversion from 'main()::<lambda()>' to non-scalar type 'Value<double>' requested
You can try this example here.
Why does assigning work for T and not std::function<T()> and how can I make it so it does?
Note: I'm aware of this answer but I didn't become clear to me how to fix the issue without having to explicitly call the constructor like I did for Value<double> e.
Why does assigning work for T and not std::function<T()> and how can I make it so it does?
Your code does not use assignment, but copy initialization and
In addition, the implicit conversion in copy-initialization must produce T directly from the initializer, while, e.g. direct-initialization expects an implicit conversion from the initializer to an argument of T's constructor.
So to make it work you have to make your ctor to accept lambda directly (this is simplified example):
template<typename T>
class Value {
std::function<T()> get;
public:
template<class Y>
Value(Y lambda ) : get( std::move( lambda ) ) {}
};
live code You probably want to add restriction using std::enable_if or concept if C++20 is allowed to this ctor as well as in this form this constructor would try to accept everithing other overloads would not and may produce cryptic errrors. And according to this enable_if template param is lambda (with particular signature) it could be as simple as
template<class Y, typename = decltype(std::declval<Y&>()())>
Value(Y lambda ) : get( std::move( lambda ) ) {}
which supports C++14. Here is yet another live example where you can see that this constructor is not used for initializer of type int:
Value<double> d2 = 123;
prog.cpp:9:5: note: candidate template ignored: substitution failure [with Y = int]: called object type 'int' is not a function or function pointer Value(Y lambda ) : get( std::move( lambda ) ) {}
A lambda is not a std::function. That means when you do
Value<double> f = []() { return 1.; };
you need to convert []() { return 1.; } into a std::function, which is a user defined conversion, and then you need to convert that std::function into a Value<double>, which is another user defined conversion. That's two user defined conversion when you are only ever allowed up to one such conversion. This is why the code fails to compile.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With