Recently I was studying about the benefits and uses of std::optional
in C++17 (Bartek's Blog) and there I saw a line that said "std::optional
can be used for Lazy Loading of Resources" . Upon digging a little bit, I saw that C++ does not have native support for Lazy evaluation. So I just gave it a try and wrote the following code for lazy evaluation in C++.
Lazy.ixx - Visual Studio(Experimental :Modules)
To compile this (VS2019):
cl /experimental:module /EHsc /TP /MD /std:c++latest Lazy.ixx /module:interface /module:output Lazy.pcm /Fo: Lazy.obj /c
#include<optional>
#include<functional>
export module Lazy;
export namespace gstd
{
template<typename T>
class Lazy
{
std::optional<T> m_resource;
std::function<T()>m_ctor_func;
public:
Lazy(std::function<T()>ctor_func)
{
m_ctor_func=ctor_func;
}
std::optional<T> operator ->()
{
//If resource is not initialized(i.e Ctor not invoked/ first time use)
if(!m_resource)
{
m_resource=m_ctor_func();
}
return m_resource;
}
};
}
Now sorry for using modules! (I just love them). To use this class it's pretty simple:
//main.cpp
#include<iostream>
import Lazy;
// A simple class called Resource
class Resource
{
public:
Resource()
{
std::cout<<"Welcome to new C++\n";
}
Resource(int a, int b)
{
std::cout<<"This also works : "<<a<<" "<<b<<"\n";
}
void func()
{
std::cout<<"Resource::func()\n";
}
};
int main()
{
gstd::Lazy<Resource>resx([](){ return Resource(4,5); });
std::cout<<"Before construction\n";
//Some code before using the resource
resx->func();
std::cin.get();
return 0;
}
To compile this:
cl /experimental:module /module:reference Lazy.pcm /std:c++latest /TP /MD /EHsc /c /Fo: main.obj main.cpp
Get the executable by linking those two files:
cl main.obj Lazy.obj
I am pretty much new to programming so please bear me with the silly mistakes. One problem that can be seen at the first glance is this class (Lazy) is not thread-safe. Implementing that would be easy with mutexes. But, other than that, how can I improve my code?
1 Answer 1
Preface
I am using Apple Clang 10 and the following commands to build.
clang++ -std=c++17 -fmodules-ts --precompile Lazy.cppm -o Lazy.pcm
clang++ -std=c++17 -fmodules-ts -c Lazy.pcm -o Lazy.o
clang++ -std=c++17 -fmodules-ts -fprebuilt-module-path=. Lazy.o main.cpp
I'm using C++ 17 since that's the tag on the question, although I believe MSVC /std:c++latest
corresponds to the parts of C++ 20 that are already implemented on MSVC.
I had one compilation error when I first tried this:
In file included from main.cpp:3:
Lazy.cppm:10:21: error: definition of 'optional' must be imported from module 'Lazy.<global>'
before it is required
std::optional<T> m_resource;
^
main.cpp:26:24: note: in instantiation of template class 'gstd::Lazy<Resource>' requested here
gstd::Lazy<Resource>resx([](){ return Resource(4,5); });
I solved it by adding #include <optional>
in main.cpp, but maybe there's a better way. Please let me know if there is a better solution. I have only toyed around with modules so I'm far from an expert.
Fix your indentation
Minor nitpick: use consistent indentation. You have 4 spaces for most of it (which is fine) but 1 space in a few spots. And 0 spaces in a few spots too. There are tools that can do this for you automatically (although it's pretty easy to just do it by hand).
Consider using operator T
C++ can implicitly convert your object into a T:
template <typename T>
struct Lazy {
operator const T&() {
...
}
};
int main() {
Lazy<int> n(...);
return n;
}
Consider making access const
When you access an object, you don't expect to modify it. In other words, you should be able to write:
Lazy<int> const n(...);
int x = n + 2;
This implies an object that looks something like
template <typename T>
struct Lazy {
operator const T&() const {
...
}
private:
std::optional<T> mutable opt;
};
Think about what kind of functions you want to support
As you've written it now, you have two std::functions: the argument to the ctor and the one in the object. You should at most have one. You could use std::move.
Lazy(std::function<T()> ctor_func)
: m_ctor_func(std::move(ctor_func)) // at least do this!
{}
At least use a member initializer list instead of initialization by assignment.
It may be preferable to get rid of std::function completely and instead use a template parameter. This would allow you to get rid of all the copying. You could even have non-copyable types in the function object. You could even go overboard and deduce the stored type based on the templated function. Then you could just write:
Lazy const n([nc=NonCopyable()]{ return 1; });
No template arguments! It's up to you to decide whether this is a good idea, but it may be a good exercise if you are new to templates.
Use optional::emplace
... instead of assignment.
std::optional
if you always return it with the value set? \$\endgroup\$T*
, people can't do strange things with it without explicitly callingoperator->
. That's what the standard facilities do (and is the intended method). \$\endgroup\$