3

So I wrote a function that composes "sequentially" void lambdas so that I can use them at once in an algorithm:

template <typename F, typename... Fs>
auto lambdaList(F f, Fs... fs)
{
    return [=] (auto&... args) { f(args...); lambdaList(fs...)(args...); };
}

template <typename F>
auto lambdaList(F f)
{
    return [=] (auto&... args) { f(args...); };
}

It works if I use local lambdas, but not when I use functions in a different namespace:

#include <iostream>

namespace foo {
    void a() { std::cout << "a\n"; }
    void b() { std::cout << "b\n"; }
}

template <typename F, typename... Fs>
auto lambdaList(F f, Fs... fs)
{
    return [=] (auto&... args) { f(args...); lambdaList(fs...)(args...); };
}

template <typename F>
auto lambdaList(F f)
{
    return [=] (auto&... args) { f(args...); };
}

int main() {
    auto printStarBefore = [] (const std::string& str) { 
        std::cout << "* " + str; 
    };
    auto printStarAfter = [] (const std::string& str) { 
        std::cout << str + " *" << std::endl; 
    };    

    lambdaList(printStarBefore, printStarAfter)("hi");  // ok
    lambdaList(foo::a, foo::b)();                       // error
}

The error is no matching function for call to 'lambdaList()' with:

main.cpp:11:56: note:   candidate expects at least 1 argument, 0 provided
     return [=] (auto&... args) { f(args...); lambdaList(fs...)(args...); };
                                              ~~~~~~~~~~^~~~~~~

Why does it sometimes work but sometimes not?

Barry
  • 286,269
  • 29
  • 621
  • 977
Lemko
  • 344
  • 3
  • 10
  • I editted your example to be completely self-contained. In the future, our guidelines for good questions are to provide a [mcve]. – Barry May 07 '16 at 16:58

1 Answers1

6

You need to invert your functions:

template <typename F>
auto lambdaList(F f)
{
    return [=] (auto&... args) { f(args...); };
}

template <typename F, typename... Fs>
auto lambdaList(F f, Fs... fs)
{
    return [=] (auto&... args) { f(args...); lambdaList(fs...)(args...); };
}

As-is, your base case won't be found by unqualified lookup in your recursive case - it can only be found by argument dependent lookup. If the arguments aren't in the same namespace as lambdaList, then it won't be found at all and the recursive step will always call itself. That's the source of your error.

The new ordering allows the base-case lambdaList() to be found by normal unqualified lookup - now it's visible at the point of definition of the recursive lambdaList().


That said, we can do better. Write one function that invokes everything:

template <typename... Fs>
auto lambdaList(Fs... fs) { 
    using swallow = int [];
    return [=](auto const&... args) {
        (void)swallow{0,
            (void(fs(args...)), 0)...
        };
    };
}

And now we don't need to worry about any kind of lookup. If you have access to a modern-enough compiler that supports some C++1z features, the above can be greatly reduced with:

template <typename... Fs>
auto lambdaList(Fs... fs) { 
    return [=](auto const&... args) {
        (fs(args...), ...);
    };
}

That's downright understandable!

Barry
  • 286,269
  • 29
  • 621
  • 977
  • "As-is, your base case won't be found by lookup in your recursive case - it will always call itself." [Citation needed](http://ideone.com/aO86oA). – n. m. could be an AI May 07 '16 at 16:50
  • @n.m. Try the new example in the question. – Barry May 07 '16 at 16:56
  • Thanks, I didn't think about the namespaces... Could you please explain what does the (void)int[]{0,(void(fs(args...)),0)... do here? Is it a common pattern? – Lemko May 07 '16 at 17:23
  • 2
    @lemko it is a hack around the difficulty of expanding pack expressions in a way that causes them to be executed left to right. Here we create an array of ints (all 0) then discard it, and as a side effect expand a pack of expressions and run them in guaranteed order. – Yakk - Adam Nevraumont May 07 '16 at 17:48
  • 1
    @Lemko Take a look at [this answer](http://stackoverflow.com/a/30563282/2069064). It's probably the best possible explanation. – Barry May 07 '16 at 18:39