7
\$\begingroup\$

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?

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Why are you storing/returning std::optional if you always return it with the value set? \$\endgroup\$
    – D. Jurcau
    Oct 3, 2019 at 17:07
  • \$\begingroup\$ I am returning std::optional<T> because of the problem with operator-> According to docs "It has additional, atypical constraints: It must return an object (or reference to an object) that also has a pointer dereference operator, or it must return a pointer that can be used to select what the pointer dereference operator arrow is pointing at." ** Now this behavior could be emulated by converting that to pointer and returning it but it will introduce a whole new set of problems among which the deadliest one is that people may get a false sense of assumption that the returned obj is a pointer \$\endgroup\$ Oct 4, 2019 at 0:21
  • \$\begingroup\$ Which may make people do weird stuff like deleting that pointer or trying to assign something else. On the other hand I feel that returning a std::optional<T> would keep the users aware about what type of object they are dealing with \$\endgroup\$ Oct 4, 2019 at 0:23
  • \$\begingroup\$ Also using std::optional sometimes does have a memory penalty due to alignment issues \$\endgroup\$ Oct 4, 2019 at 0:25
  • \$\begingroup\$ Just return a T*, people can't do strange things with it without explicitly calling operator->. That's what the standard facilities do (and is the intended method). \$\endgroup\$
    – L. F.
    Oct 8, 2019 at 10:19

1 Answer 1

1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.