Sunday, May 04, 2014

Fun with Lambdas: C++14 Style (part 2)


Look at some interesting examples of C++11/14 lambdas and how they interact with other language features and libraries. I hope to find some time to add some explanations. See part 1 if you missed it.

  • Associative containers and lambdas
    std::set<int, std::function<bool(int, int)>> 
      numbers([](int i, int j) { return i < j; });
    
  • Recursive Lambdas (see Creating recursive lambdas and returning them too!)
    auto make_fibo() 
    {
      return [](int n) {
        std::function<int(int)> recurse;
        recurse = [&](int n){ 
           return (n<=2)? 1 : recurse(n-1) + recurse(n-2); 
        }; 
        return recurse(n);
      };
    }
    
  • Composable list manipulation (e.g., cpplinq, narl, LEESA)
    Box boxes[] = { ... };
    int sum_of_weights = 
         cpplinq::from_array(boxes)
      >> where([](const Box & box) { 
           return box.color == Color.RED;
         })
      >> select([](const Box & box) {
           return box.get_weight();
         })
      >> sum();
    
    
  • Overloaded Lambdas
    template <class... F>
    struct overload : F... {
      overload(F... f) : F(f)... {}  
    };
    
    template <class... F>
    auto make_overload(F... f) {
      return overload<F...>(f...);   
    }
    
    auto f = 
        make_overload([](int i) { /* print */ },
                      [](double d) { /* print */ });
    f(10); // int 
    f(9.99); // double
    
  • Type Switch (simple pattern matching) (see type_switch.cpp and this paper)
    struct Base { 
      virtual ~Base() {} 
    };
    struct Derived : Base {};
    
    template <class Poly>
    void test(Poly& p) {  
      match(p)(
        [](int i)             { cout << "int";       },
        [](std::string &)     { cout << "string";    },
        [](Derived &)         { cout << "Derived";   },     
        [](Base &)            { cout << "Base";      },    
        otherwise([](auto x)  { cout << "Otherwise"; })
      );  
    }
    Derived d;
    Base &b = d;
    std::string cpptruths = "C++ Truths";
    boost::any something = cpptruths;
    
    test(10);        // int
    test(cpptruths); // string
    test(something); // string
    test(b);         // Derived
    test(9.99);      // Otherwise
    
  • Converting shared_ptr between boost and std (see StackOverflow)
    template <typename T>
    boost::shared_ptr<T> 
    make_shared_ptr(std::shared_ptr<T> ptr) 
    {      
      return boost::shared_ptr<T>(ptr.get(), 
        [ptr](T*) mutable { ptr.reset(); });
    }
    
    template <typename T>
    std::shared_ptr<T> 
    make_shared_ptr(boost::shared_ptr<T> ptr)
    {      
      return std::shared_ptr<T>(ptr.get(), 
        [ptr](T*) mutable { ptr.reset(); });
    }
    
  • In-place parameter pack expansion 
    template <class... T>
    void foreach(T... args) 
    {  
      bool b[] = { [=](){ 
        std::cout << args << "\n"; 
        return true; 
      }()... }; 
    }
    
    foreach(10, 20.2, true);
    
  • Memoization (see original)
    template <typename ReturnType, 
              typename... Args>
    auto memoize(ReturnType (*func)(Args...))
    {
        std::map<std::tuple<Args...>, ReturnType> cache;
    
        return ([=](Args... args) mutable  
        {
            std::tuple<Args...> t(args...);
            if (cache.find(t) == cache.end())                
            {
              std::cout << "not found\n";
              cache[t] = func(args...);
            }
            return cache[t];
        });
    }
    
  • Finally, slides




7 comments:

Amber said...

should `sum_of_weights` have been `count_of_weights`? (that'd make it a bit trivial). Or perhaps `count()` is the mistake

(I surely hope count doesn't sum)

Amber said...

The memoization example is nice and succinct, but it strikes me as significantly less advanced than others I've seen years ago on your blog, and even somewhat less featured than e.g. Herb's popular example.

Is there a reason you prefer this one, beyond the clarity of exposition?

Seth said...

I really like the overload example. Something like that should be in the standard library (though more general, so it works for any callable entity, not just inheritable classes.)

The 'In-place parameter pack expansion' example relies on function arguments being evaluated in a certain order. Instead you can avoid no_op(...) and do:
{bool b[] = {[=]() { cout << args << "\n"; return true; }()...};}

Nagy said...

Here is the original discussion for make_shared_ptr:

http://stackoverflow.com/questions/12314967/cohabitation-of-boostshared-ptr-and-stdshared-ptr/12315035#12315035

Sumant Tambe said...

@Amber and @Seth: Right. Thanks. I fixed the code.

y121516 said...

The Overloaded Lambdas code is ill-formed.
It work on Clang, but it is error occurred on GCC.
And behavior of GCC is correct.

Please see a entry and well-formed code written by @fimbul11
http://fimbul.hateblo.jp/entry/2014/05/10/162852

Lee said...

Nice couple of posts, Sumant. Made me want to dig into this a bit more. I've done a bit of digging, and have some more yet to do. I did find a way to compose lambdas (and, by extension, functions).

I've got a blog post on that at http://learnmeahaskell.blogspot.com/2014/05/composing-lambdas-in-c14-and-functions.html