Diego Saraiva (4) [Avatar] Offline
#1
The code into listing 6.3 presented some issues during the compilation. On GCC-7 the compiler reclaims:

warning: there are no arguments to 'm_computation' that depend on a template parameter, so a declaration of 'm_computation' must be available [-fpermissive]

The trouble is even worst into clang. The compiler doesn't accept it printing the following message:

use of undeclared identifier 'm_computation' operator decltype(m_computation()) () const


So, I made a something like a "forward definition" and it works. I think that you would like to know about this issue.

template<typename F>
class lazy_val
{
    F m_computation; // forward definition
    public:
        lazy_val(F computation) : m_computation(computation), m_cache_initialized(false)
    {}
    operator decltype(m_computation()) () const
    {
        std::unique_lock<std::mutex> lock{m_cache_mutex};
        if(not m_cache_initialized)
        {
            m_cache = m_computation();
            m_cache_initialized = true;
        }
        return m_cache;
    }
    private:
        mutable bool m_cache_initialized;
        mutable decltype(m_computation()) m_cache;
        mutable std::mutex m_cache_mutex;
};



My default C++ compiler:

Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.6.0
Thread model: posix


Thanks for your attention.
Best Regards
Diego Saraiva.
Ivan Cukic (97) [Avatar] Offline
#2
Hi Diego,

Thanks for this. I've switched the privates to be above the public parts altogether. There will be a full lazy_val implementation in the accomapnying code examples (a bit different than this one - it will use optional<T>smilie.

Cheers,
Ivan
Sumant Tambe (17) [Avatar] Offline
#3
Should the lazyval::operator() return a const reference?
Ivan Cukic (97) [Avatar] Offline
#4
Yes, a operator const T& could be useful. I'll see to add it.

Thanks!
Pascal Menuet (6) [Avatar] Offline
#5
I think there are errors/non-optimal code in figure Listing 6.2:
- the function make_lazy_val should return lazy_val<std::decay_t<F>> or else it produce strange things with l-value arguments.
- the constructor of lazy_val should std::move its parameter (or even template it and perfect-forward it)

Another similar issue in Listing 6.9: the constructor of memoize_helper does not forward its first parameter f
Ivan Cukic (97) [Avatar] Offline
#6
I'm torn on this one.

With the decay, the lazy_val will copy the function object (safer, but less efficient), and without it will save the reference to it (efficient, not safe).

You are right about the moves. Will see what to do about it - I tried to make the snippets in the book simpler, but this was maybe a wrong place to simplify.
Pascal Menuet (6) [Avatar] Offline
#7
To my mind, if you don't use decay_t, there is a deeper problem of consistency:
- if the argument provided to make_lazy_val is an r-value then the type F will be deduced as a plain type, so you will store a plain type inside lazy_val.
- if the argument provided to make_lazy_val is an l-value then the type F will be deduced as an l-value reference, so you will store a reference inside lazy_val.
Ivan Cukic (97) [Avatar] Offline
#8
I agree. I'll probably change it, and if someone wants a reference, there is always std::ref - to mimic the behaviour of STL algorithms.
Hans-J. Schmid (16) [Avatar] Offline
#9
Hi Ivan,

could you explain in some more words how this macro works?

#define lazy make_lazy_val_helper - [=]


Kind regards!
Ivan Cukic (97) [Avatar] Offline
#10
make_lazy_val_helper is an instance of an object which has a operator- defined on it (it can be declared as an inline variable in C++17).

This is a dummy object. The only important thing is the operator- defined on it. It takes a function object, and creates an instance of lazy_val.

The macro calls the operator- on the dummy object, and as the right argument, it begins to define a lambda - it defines the lambda head which captures everything, and leaves the body to be defined later.

auto v = lazy {
    return 42;
};


gets expanded into

auto v = make_lazy_val_helper - [=] {
    return 42;
}


which, if we write the operator call as a normal function call, becomes

auto v = make_lazy_val_helper.operator-(
    [=] {
        return 42;
    }
);


And this (because of how operator- is implemented) is the same as:

auto v = make_lazy_val(
    [=] {
        return 42;
    }
);


Tricks like these can be quite cool, but they should not be overused.
Hans-J. Schmid (16) [Avatar] Offline
#11
OK, got it. Thanks a lot!