0

I created the following class to create values of any type which are either fixed or recalculated everytime the call operator is used on them:

template <typename T>
class DynamicValue {
    private:
    std::variant<T, std::function<T()>> getter;

    public:
    DynamicValue(const T& constant) : getter(constant){};
    template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
    DynamicValue(F&& function) : getter(function) {}
    DynamicValue(const T* pointer) : DynamicValue([pointer]() { return *pointer; }) {}
    DynamicValue(const DynamicValue& value) : getter(value.getter) {}
    ~DynamicValue() {}
    DynamicValue& operator=(const DynamicValue& value) {
        getter = value.getter;
        return *this;
    }
    T operator()() {
        return getter.index() == 0 ? std::get<T>(getter) : std::get<std::function<T()>>(getter)();
    }
};

I also wrote the following dummy struct to showcase my issue:

struct A {
    int b;
};

The problem is, ideally, I'd be able to initialize any DynamicValue<T> as if it were of type T. So, in this example, because I can do A a = {1};, instead of having to write DynamicValue<A> a = A{1};, I'd be able to do DynamicValue<A> a = {1};. However, when I attempt to do so, I get the folowing error:

could not convert '{1}' from '' to 'DynamicValue'

You can try a live example here.

Is there anyway to overcome this issue or must I accept the longer syntax?

cabralpinto
  • 1,814
  • 3
  • 13
  • 32
  • related/dupe: https://stackoverflow.com/questions/39645986/constructor-using-stdforward – NathanOliver Jan 20 '21 at 13:15
  • `= {1}` is an initialization syntax for aggregates, which is not available for non-aggregate classes. Instead of that you should use unified initialization syntax `{1}`. – Andrey Semashev Jan 20 '21 at 13:15
  • Braced initialization lists have no type and, as such, are quite difficult to work with in the context of templates. – Sam Varshavchik Jan 20 '21 at 13:16

1 Answers1

0

You can add a constructor which takes all parameters which a type T needs and forward them to the constructor of T and initialize your internal value getter.

Simply add the following lines to your class:

template < typename ... S> DynamicValue( S&& ... parms ): getter{T{ std::forward<S...>( parms... )}} {}

BUT! if we do this, it will also be invoked if we pass a non const DynamicValue. So we have to exclude it via SFINAE and get:

template <typename F,
   typename = std::enable_if_t<std::is_invocable_v<F>&&    !std::is_same_v<std::remove_reference_t<F>, DynamicValue<T>>, bool>>
            DynamicValue(F&& function) : getter(function) {}

But there is another problem:

Your code already has something which can be invoked as a copy constructor which is not intended!

The constructor:

template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
    DynamicValue(F&& function) : getter(function) {}

will also be invoked, as DynamicType<T> is also invocable! We must also use the second condition to disable the instantiation of type DynamicType<T> which results in:

template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>&& !std::is_same_v<std::remove_reference_t<F>, DynamicValue<T>>, bool>>
            DynamicValue(F&& function) : getter(function) {}

You are able to create an instance with:

DynamicValue<A> a1 = A{1};
DynamicValue<A> a2{1};

Using = {1} will not work.

And now even the fragment

DynamicValue<A> a9{100};
DynamicValue<A> a10(a9);
std::cout << a10().b << std::endl;

works as expected.

Klaus
  • 24,205
  • 7
  • 58
  • 113