0

In the following example, I would like a traverse method that receives a callback. This example works perfectly as soon as I don't capture anything [] because the lambda can be reduced into a function pointer. However, in this particular case, I would like to access sum.

struct Collection {
    int array[10];

    void traverse(void (*cb)(int &n)) {
        for(int &i : array)
            cb(i);
    }

    int sum() {
        int sum = 0;
        traverse([&](int &i) {
            sum += i;
        });
    }
}

What is the proper way (without using any templates) to solve this? A solution is to use a typename template as follows. But in this case, you lack visibility on what traverse gives in each iteration (an int):

template <typename F>
void traverse(F cb) {
    for(int &i : array)
        cb(i);
}
cigien
  • 57,834
  • 11
  • 73
  • 112
nowox
  • 25,978
  • 39
  • 143
  • 293
  • @max66 so the proper answer to this question would be : no there is no solution without pointers. – nowox Oct 11 '20 at 15:07
  • @max66 I have edited my question for a more specific aspect of this. – nowox Oct 11 '20 at 15:09
  • Please read the dupe. If you have some specific question that is not covered there, or it doesn't answer your question, please edit the question. – cigien Oct 11 '20 at 15:10
  • 3
    You can also use a `std::function`. It incurs some overhead, but the function will no longer be a template. – HolyBlackCat Oct 11 '20 at 15:11
  • *"We cannot guess anymore that traverse requires a lambda that receives an int"* Write a comment on the function and explain that in the comment. Or you can constrain the template with [concepts](https://en.cppreference.com/w/cpp/concepts/invocable) or regular SFINAE. (If you decide to use concepts/traits that have the word "invokable" in them, don't forget to call the function with `std::invoke` instead of regular parentheses.) – HolyBlackCat Oct 11 '20 at 15:12
  • @HolyBlackCat NO! Comments should not explain such things! The code should be written to be self understandable. – nowox Oct 11 '20 at 15:13
  • Ok, I reopened. – cigien Oct 11 '20 at 15:15
  • *"code should be written to be self understandable"* Then concepts & SFINAE are your friends. I would only resort to `std::function` if I had to put the implementation to a .cpp file. – HolyBlackCat Oct 11 '20 at 15:17
  • 1
    And last, you don't want your method to "only take a lambda". What about normal functions and non-lambda function objects? – HolyBlackCat Oct 11 '20 at 15:20
  • This is a [dupe](https://stackoverflow.com/questions/47698552/how-to-check-if-template-argument-is-a-callable-with-a-given-signature) now. – cigien Oct 11 '20 at 15:22

3 Answers3

2

Lambda types are unspecified; there is no way to name them.

So you have two options:

  • Make traverse a template (or have it take auto, which is effectively the same thing)

    Fortunately this is a completely normal and commonplace thing to do.

  • Have traverse take a std::function<void(int)>. This incurs some overhead, but does at least mean the function need not be a template.

But in this case, you lack visibility on what traverse gives in each iteration (an int)

We don't tend to consider that a problem. I do understand that giving this in the function's type is more satisfying and clear, but generally a comment is sufficient, because if the callback doesn't provide an int, you'll get a compilation error anyway.

Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35
2

Only captureless lambdas can be used with function pointers. As every lambda definition has its own type you have to use a template parameter in all places where you accept lambdas which captures.

But in this case, you lack visibility on what traverse gives in each iteration (an int).

This can be checked easily by using SFINAE or even simpler by using concepts in C++20. And to make it another step simpler, you even do not need to define a concept and use it later, you can directly use an ad-hoc requirement as this ( this results in the double use of the requires keyword:

struct Collection {
    int array[10];


    template <typename F>
        // check if F is "something" which can be called with an `int&` and returns void.
        requires requires ( F f, int& i) { {f(i)} -> std::same_as<void>; }
        void traverse(F cb) 
        {   
            for(int &i : array)
                cb(i);
        }

     // alternatively you can use `std::invocable` from <concepts>

    // check if F is "something" which can be called with an `int&`, no return type check 
    template <std::invocable<int&> F>
        void traverse2(F cb) 
        {   
            for(int &i : array)
                cb(i);
        }   

    int sum() {
        int sum = 0;
        traverse([&](int &i) {
            sum += i;
        });

        return sum;
    }
};

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • That's much easier to spell `template F>` rather than using the long `requires` clause. Though you'll need to use `std::invoke` when calling it. – Nicol Bolas Oct 11 '20 at 16:07
  • No, I mean the [`invocable` *concept*](https://en.cppreference.com/w/cpp/concepts/invocable), which is why it works in the template header in the code I typed. And who cares about the return type? It wouldn't cause any harm for a user to pass a function that returned a value. – Nicol Bolas Oct 11 '20 at 16:11
  • @NicolBolas OK, did not know about :-) Will add it! Thanks – Klaus Oct 11 '20 at 16:15
0

In your case you have several ways of declaring a callback in C++:

Function pointer

void traverse(void (*cb)(int &n)) {
    for(int &i : array)
        cb(i);
}

This solution only supports types that can decay into a function pointer. As you mentioned, lambdas with captures would not make it.

Typename template

template <typename F>
void traverse(F cb) {
    for(int &i : array)
        cb(i);
}

It does accept anything, but as you noticed. the code is hard to read.

Standard Functions (C++11)

void traverse(std::function<const void(int &num)>cb) {
    for(int &i : array)
        cb(i);
}

This is the most versatile solution with a slightly overhead cost.

Don't forget to include <functional>.

nowox
  • 25,978
  • 39
  • 143
  • 293
  • 1
    I don't understand. All of this is covered in the question that was marked as the dupe target, which you said didn't even answer your question. – cigien Oct 11 '20 at 15:37
  • Perhaps it is answered but no examples were mentioned. I am not an expert in C++ the dup you mentioned was hard to read and understand. It took me time to understand that `std::function` was what I was looking for (and I still not understand how much is the overhead). – nowox Oct 11 '20 at 15:38
  • 1
    That's reasonable, but the question is still a dupe. And there are plenty of examples there. Can you clarify in what way your answer adds additional information? (and if you feel it does, I suggest writing an answer on the dupe itself). – cigien Oct 11 '20 at 15:41
  • To me, it is still a different question. One is about function signature in templates, which requires the knowledge of what is a signature and what is a template. Mine is about callbacks in methods. I am pretty sure someone that is looking for callbacks examples would click on my question, not that dup. Am I wong? – nowox Oct 11 '20 at 15:43
  • @nowox: "*I am pretty sure someone that is looking for callbacks examples would click on my question, not that dup.*" And that's *exactly* why we have duplicates. So that a *seemingly* unrelated question can point directly to the correct information without having to repeat it in a different place. Having a question marked as a duplicate is not a bad thing. – Nicol Bolas Oct 11 '20 at 16:09
  • Returning `const void` is kinda weird. – Asteroids With Wings Oct 11 '20 at 16:42