6

I have a class Derived that inherits from class Base<ResourceType>:

template <class ResourceType>
class Base {
protected:
  ResourceType* resource;
public:
  void set_resource(ResourceType* resource) {
    this->resource = resource;
  }
};

template <class ResourceType>
class Derived : public Base<ResourceType> {
public:
  using Base<ResourceType>::resource;
  void print () {
    std::cout << *resource << std::endl;
  }
};

I want to create a factory that creates objects of type Derived. I can of course do this with functions:

template <typename ResourceType>
auto derived_factory () { 
  return new Derived<ResourceType>(); 
}

auto derived = *(derived_factory<int>());

But, I am not able to write a lambda function for the factory. I can write templated lambda functions if I was accepting a template argument using the auto keyword, but here I just want to use the template to determine the return type. The following fails:

auto derived_factory = []<typename ResourceType>() {
  return new Derived<ResourceType>();
};

auto derived = *(derived_factory<int>());

with the error:

inherit_unknown_type.cpp: In function ‘int main()’:
inherit_unknown_type.cpp:27:36: error: expected primary-expression before ‘int’
   auto derived = *(derived_factory<int>());
                                    ^~~
inherit_unknown_type.cpp:27:36: error: expected ‘)’ before ‘int’

Am I just calling the lambda incorrectly? Or do I have to wait for C++20?

max66
  • 65,235
  • 10
  • 71
  • 111
Sagar Jha
  • 1,068
  • 4
  • 14
  • 24
  • 1
    I guess you can use a dummy argument of a phantom type, but why do you want a lambda in the first place? – n. m. could be an AI Jul 19 '18 at 17:00
  • I guess for the same reasons as to why people prefer a lambda over a function. We use lambdas at other places, so it will be consistent with the code. – Sagar Jha Jul 19 '18 at 17:57
  • I don't know why anyone would indiscriminately prefer lambdas. If you give it a name and there's no capture, it can just as well be a regular function. – n. m. could be an AI Jul 19 '18 at 18:18
  • I guess you do have a point. To give you more of the context, our library provides a class whose object creates objects of user-defined classes using user supplied factory functions. These factory functions exist solely for supplying to the constructor of our library class. And C++ does not support nested functions. Writing lambdas instead of functions is arguably neater for the user as they can just inline the lambdas in the constructor call. Users writing functions that they don't call themselves, but just pass to the constructor is less than ideal. – Sagar Jha Jul 19 '18 at 18:35
  • 1
    OK if the function is user's code then there's no reason to not allow it to be a lambda. If C++20 is not an option I would use tag dispatch (I called it phantom type in a comment above). – n. m. could be an AI Jul 20 '18 at 14:53
  • Makes sense. We are still using g++-6.4. Until we upgrade g++, tag dispatch is the way to go. – Sagar Jha Jul 20 '18 at 15:47

3 Answers3

9

Template parameter lists in lambda expressions is a C++20 feature.

(In fact, my GCC says that in the diagnostic: error: lambda templates are only available with -std=c++2a or -std=gnu++2a [-Wpedantic])

But you don't have to wait for C++20, it's already supported by GCC 8 with -std=c++2a flag.

And you'll have to change the call syntax: Instead of derived_factory<int>(), you need derived_factory.operator()<int>().


As an alternative (if you don't want a free function), I suggest using a variation of tag dispatch:

auto derived_factory = [](auto tag) {
    return new Derived<typename tag::type>();
};

template <typename T> struct tag_type {using type = T;};

// Usage:
derived_factory(tag_type<int>{})

Also, even if you make it compile somehow, this line:

auto derived = *(derived_factory<int>());

will cause a memory leak no matter what. To avoid that, you should store the result as a pointer or a reference. Or even better, use a smart pointer.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 1
    You don't need to declare `tag_type`. In C++17, you can use `std::common_type` and in C++20 even `std::type_identity`. – Chronial Apr 02 '20 at 23:25
7

Waiting for C++20, you can return the lambda from a template class

template <typename ResourceType>
auto make_derived_factory ()
 { return []{ return new Derived<ResourceType>{}; }; }

auto derived = make_derived_factory<int>();

int main ()
 {
   auto df { derived() };
 }
max66
  • 65,235
  • 10
  • 71
  • 111
1

The above doesn't work, but this does:

auto derived_factory = [](auto tag) 
{
    return new Derived<decltype(tag)::type>();
};

template <typename T> struct tag_type {using type = T;};

// Usage:
derived_factory(tag_type<int>{})
Alexey E
  • 11
  • 1