-3
#include <iostream>
#include <string>

typedef std::string S;

template <typename T>
static inline T* getOrCreate( ( []() -> auto) creationSpren *) {
    bool assigned = false;

    if (!assigned) {
        // invoke creationSpren with passed arguments 
        // assign
    }
}

int main()
{
    auto& xx = []() {
        return new std::string("abc");
    };

    auto& zzz = getOrCreate<S>(xx);
}

note: this code does not compile, that is the problem I am trying to solve.

however, I wrote this minimum example to illustrate the problem, it is as barebones as possible.

What I'm trying to achieve is simple, to use lambdas to achieve lazy initialization of an object, when it is needed (i.e. when a retrieve fails, it calls the lambda and assigns the object (i.e. stores it) and returns it)

What I have problems with, as I have no experience with lambdas is both the signatures.

That is what I am asking, how to write the 2 lambda signatures. thanks.

and yes, it needs to be templated.

verbatim errors

<source>: In lambda function:
<source>:7:45: error: expected '{' before ')' token
    7 | static inline T* getOrCreate( ( []() -> auto) creationSpren *) {
      |                                             ^
<source>: At global scope:
<source>:7:46: error: expected ')' before 'creationSpren'
    7 | static inline T* getOrCreate( ( []() -> auto) creationSpren *) {
      |                             ~                ^~~~~~~~~~~~~~
      |                                              )
<source>:7:63: error: expected ';' before '{' token
    7 | static inline T* getOrCreate( ( []() -> auto) creationSpren *) {
      |                                                               ^~
      |                                                               ;
<source>: In function 'int main()':
<source>:18:16: error: cannot bind non-const lvalue reference of type 'main()::<lambda()>&' to an rvalue of type 'main()::<lambda()>'
   18 |     auto& xx = []() {
      |                ^~~~~~
   19 |         return new std::string("abc");
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   20 |     };
      |     ~           
<source>: In instantiation of 'std::__cxx11::basic_string<char>* getOrCreate<std::__cxx11::basic_string<char> >':
<source>:22:17:   required from here
<source>:7:33: error: cannot convert '<lambda()>' to 'std::__cxx11::basic_string<char>*' in initialization
    7 | static inline T* getOrCreate( ( []() -> auto) creationSpren *) {
      |                                 ^~~~~~~~~~~~
      |                                 |
      |                                 <lambda()>
<source>:22:31: error: 'getOrCreate<std::__cxx11::basic_string<char> >' cannot be used as a function
   22 |     auto& zzz = getOrCreate<S>(xx);
      |                 ~~~~~~~~~~~~~~^~~~
ASM generation compiler returned: 1
<source>: In lambda function:
<source>:7:45: error: expected '{' before ')' token
    7 | static inline T* getOrCreate( ( []() -> auto) creationSpren *) {
      |                                             ^
<source>: At global scope:
<source>:7:46: error: expected ')' before 'creationSpren'
    7 | static inline T* getOrCreate( ( []() -> auto) creationSpren *) {
      |                             ~                ^~~~~~~~~~~~~~
      |                                              )
<source>:7:63: error: expected ';' before '{' token
    7 | static inline T* getOrCreate( ( []() -> auto) creationSpren *) {
      |                                                               ^~
      |                                                               ;
<source>: In function 'int main()':
<source>:18:16: error: cannot bind non-const lvalue reference of type 'main()::<lambda()>&' to an rvalue of type 'main()::<lambda()>'
   18 |     auto& xx = []() {
      |                ^~~~~~
   19 |         return new std::string("abc");
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   20 |     };
      |     ~           
<source>: In instantiation of 'std::__cxx11::basic_string<char>* getOrCreate<std::__cxx11::basic_string<char> >':
<source>:22:17:   required from here
<source>:7:33: error: cannot convert '<lambda()>' to 'std::__cxx11::basic_string<char>*' in initialization
    7 | static inline T* getOrCreate( ( []() -> auto) creationSpren *) {
      |                                 ^~~~~~~~~~~~
      |                                 |
      |                                 <lambda()>
<source>:22:31: error: 'getOrCreate<std::__cxx11::basic_string<char> >' cannot be used as a function
   22 |     auto& zzz = getOrCreate<S>(xx);
      |                 ~~~~~~~~~~~~~~^~~~
Execution build compiler returned: 1
ycomp
  • 8,316
  • 19
  • 57
  • 95
  • A [mcve] should include the verbatim compiler error messages as well. – πάντα ῥεῖ Jan 13 '23 at 18:30
  • 1
    what you have seems like a complicated version of singleton with static local instance. – apple apple Jan 13 '23 at 18:46
  • not really sure.. I know what a singleton is, not really how it applies here. But I didn't explain what I'm doing sufficiently I guess... because I wanted to keep it minimally simple. But think of it something like the STL map function you can retrieve a value or have one created for you. It's that kind of thing however I don't store it in a map, but instead use a function of a host program that stores "persistent pointers" to a "slot" (index) for instance of my code that is currently runing. multiple instances can be running "at the same time" but not really as that part is single threaded. – ycomp Jan 13 '23 at 19:09

2 Answers2

1

If you look at the standard library and their functions which takes a callable object, it uses templates.

I recommend that for your function as well:

template<typename T, typename F>
static inline T* getOrCreate(F creationSpren)
{
    // ...
}

There's another problem with your current function: The variable assigned is a normal local variable. It will be created and initialized to false each time getOrCreate is called.

You need to make it a static local variable.


If you need to pass argument to getOrCreate that are then forwarded to the creationSpren function, then use template parameter packs:

#include <utility>
#include <iostream>

template<typename T, typename F, typename ...A>
static inline T* getOrCreate(F creationSpren, A&& ...args)
{
    creationSpren(std::forward<A>(args)...);
    return nullptr;
}

int main()
{
    auto lambda_noargs = []() { std::cout << "No arguments\n"; };
    auto lambda_twoargs = [](int, int) { std::cout << "Two arguments\n"; };

    getOrCreate<int>(lambda_noargs);
    getOrCreate<int>(lambda_twoargs, 1, 2);
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • could you explain a bit more, I'm also new to templates - today was my first attempt at writing any. I don't understand the concept of multiple template variables – ycomp Jan 13 '23 at 18:33
  • maybe I should look into what a callable is, you mean use that instead of a lambda, right? - all I want to do is to be able to create a templated object from within a templated function with passed in parameters.. if I didn't need the parameters, I would have be finished a long time ago. – ycomp Jan 13 '23 at 18:35
  • 1
    @ycomp A lambda *is* a callable object. By using a template like that you can use lambdas, any other object with a suitable overloaded function-call operator, and normal (non-member) functions. Inside the `getOrCreate` function you can use it like any other function, i.e. `creationSpren(); // Call the function` – Some programmer dude Jan 13 '23 at 18:37
  • 2
    @ypcomp `( []() -> auto)` isn't the type of a lambda, lambdas use some compiler specific unnamed type with a different type for every lambda, by using a template `F` will match whichever lambda you use – Alan Birtles Jan 13 '23 at 18:41
  • ah sorry about that I just added the `assigned` thing at the last minute so people wouldn't be questioning why the function did nothing and to show there was a check involved. The actual variable is stored as a pointer by a special function in a special location, that is beyond the scope of a minimum example – ycomp Jan 13 '23 at 18:46
  • The STL usually uses perfect forwarding for callable template parameters, there are very good reasons for that. – Michaël Roy Jan 13 '23 at 19:00
  • thanks I will google what that is - just kidding I will "you" it – ycomp Jan 13 '23 at 19:12
  • @MichaëlRoy just starting to read about them but is this something where I can accidentally send too many arguments to the function or arguments of different types than I had initially planned? – ycomp Jan 13 '23 at 19:20
  • @AlanBirtles thanks that helps immensely and was what I was banging my head over for so many hours today, I just couldn't understand how I was typing it wrongly.. and the actual word "lambda" in `auto` in _VS_ didn't help my understanding either.. up until then I thought `auto` was a magic way to tell me what the type was – ycomp Jan 13 '23 at 21:57
  • @ycomp Hey, this is c++, you can't do anything like passing too many arguments or arguments of the wrong type without the compiler complaining about it. – Michaël Roy Jan 13 '23 at 21:59
  • **note to self:** some dude pointed me in the right direction but I still needed to fill in some blanks due to not knowing concepts regarding templates and first class funcs in C++. Here is the page with all the clues needed to fill in the blanks: * [c++ - How to check if template argument is a callable with a given signature - Stack Overflow](https://stackoverflow.com/questions/47698552/how-to-check-if-template-argument-is-a-callable-with-a-given-signature) - take note particularly of the word "Signature" - and also the magic brackets prefix of `[]` – ycomp Jan 13 '23 at 22:38
  • therefore I can call, well pass not call, the function as `[]()` - if I'm using it, which as a test case right now is `using Signature = void(int, double);` and then I can call it from the template function as `creationSpren(1, 2.0);` , which seems to compile – ycomp Jan 13 '23 at 22:40
  • therefore my next idea would be, to use elipses instead.. although this is not really type safe.. but it will be easy if it works – ycomp Jan 13 '23 at 22:42
  • as I'm really uncertain about how to pass the signature parameter values itself into the template `getOrCreate()` function and then use that signature set of values when invoking the function, I _guess_ this is possible – ycomp Jan 13 '23 at 22:43
  • 1
    @ycomp Added example on how to use *parameter packs* to pass arguments. – Some programmer dude Jan 14 '23 at 03:31
0

what you have now is just a complicated version of static local variable. (usually used in singleton)

int main(){
    auto xx = []{
        static std::string* v = new std::string("abc");
        return v;
    };

    std::string* a = xx();
    std::string* b = xx();
    assert(a==b);

    delete a; // manually cleanup as you `new` it
}

https://godbolt.org/z/GnsGaqnx3

apple apple
  • 10,292
  • 2
  • 16
  • 36
  • thanks but this is not really what I was trying to figure out, but after I was told to make a minimal example, I can understand why you took this approach. the key I was after is how to pass lambdas (with 0 or more typed arguments) that can create objects to a template function - especially the signature parts, the signatures was what I couldn't get past on the compiler the goal here being so the template function can create these objects from the parameters on the fly as needed.. kind of like caching, well it is a form of caching.. the goal is not to have to create the object on each loop – ycomp Jan 13 '23 at 20:11
  • the template functions themselve deal with allocation, deallocating or checking the existence of objects.. this is in my real code not the minimal examples that SO love so much - unforunately I first tried to make a real world example (extracted the important code) and got blasted for doing so and not making a minimal example instead, personally I think a minim example is much more confusing as it is hard to understand the true problem that way – ycomp Jan 13 '23 at 20:14
  • @ycomp it seems like contradict to say both "just need to know how to pass a lambda" and "it's complicated". – apple apple Jan 13 '23 at 20:29
  • @ycomp anyway this can and probably should be implemented as class instead of function, which can easily store the state. (and it should not depend on signature, which would not work for free function, for example) – apple apple Jan 13 '23 at 20:30