Variadic template: Perfect forwarding of integer parameter to lambda

Viewed 413

There are similar questions, but I did not find an answer that works for my problem.

Consider the following code:

#include <cassert>
#include <functional>
#include <iostream>
#include <memory>
#include <utility>

class TestClass
{
public:
   TestClass( int value): mValue( value) { }
private:
   int  mValue;
};

template< typename T> class DeferredCreator
{
public:
   template< class... Args> DeferredCreator( Args&&... args):
      mpCreator( [=]() -> T*
         { return new T( std::forward< Args>( args)...);  }
      ),
      mpObject()
   { }

   T* get() {
      if (mpObject == nullptr)
         mpObject.reset( mpCreator());
      return mpObject.get();
   }
private:
   std::function< T*( void)>  mpCreator;
   std::unique_ptr< T>        mpObject;
};


int main() {
   DeferredCreator< int>  dcInt( 42);

   assert( dcInt.get() != nullptr);
   return 0;
}

The idea is that the class DeferredCreator creates an object only when it is really needed. I got this work e.g. for strings, but I can't figure out how to pass a simple integer into my lambda.

The error message I get is:

prog.cpp:19:26: error: no matching function for call to 'forward'
         { return new T( std::forward< Args>( args)...);  }
                         ^~~~~~~~~~~~~~~~~~~
prog.cpp:36:27: note: in instantiation of function template specialization 'DeferredCreator<int>::DeferredCreator<int>' requested here
   DeferredCreator< int>  dcInt( 42);
                          ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/6.3.0/../../../../include/c++/6.3.0/bits/move.h:76:5: note: candidate function not viable: 1st argument ('const int') would lose const qualifier
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/6.3.0/../../../../include/c++/6.3.0/bits/move.h:87:5: note: candidate function not viable: 1st argument ('const int') would lose const qualifier
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    ^
2 errors generated.

I already tried to use decltype( args) as template argument for std::forward<>, but that did not help.

The code is also available here: https://ideone.com/MIhMkt

2 Answers

args... is constant because a lambda's call operator is implicitly const. So, if you make your lambda mutable, then it works:

[=]() mutable -> T*
     { return new T( std::forward< Args>( args)...);  }

The reason it didn't work with decltype(args) is that the types themselves are not const, just the call operator.

The operator() of the closure type generated by your lambda expression is const-qualified. std::forward can attempt to move args..., which are data members of the closure. const objects cannot be moved.

You can mark your lambda as mutable:

  mpCreator( [=]() mutable -> T*
     { return new T( std::forward< Args>( args)...);  }
  ),

This removes the implicit const qualfiier from the closure type's generated operator().

live example on wandbox.org

Related