Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Can I use Null Lambda in C++?

Tags:

c++

c++11

lambda

I want to declare a function like this:

template <typename Lambda>
int foo(Lambda bar) {
    if(/* check bar is null lambda */)
        return -1;
    else
        return bar(3);
}

int main() {
    std::cout << foo([](int a)->int{return a + 3;}) << std::endl;
    std::cout << foo(NULL_LAMBDA) << std::endl;
}

Then, how can I declare the NULL_LAMBDA and the condition checking passed lambda function whether is null?

like image 769
Seokmin Hong Avatar asked Aug 17 '17 06:08

Seokmin Hong


3 Answers

You can add a dedicated specialization:

#include <iostream>
#include <cstddef>

template<typename Lambda> int
foo(Lambda bar)
{
    return(bar(3));
}

template<> int
foo<::std::nullptr_t>(::std::nullptr_t)
{
    return(-1);
}

int main()
{
    ::std::cout << foo([] (int a) -> int {return(a + 3);}) << ::std::endl;
    ::std::cout << foo(nullptr) << ::std::endl;
}
like image 155
user7860670 Avatar answered Oct 18 '22 18:10

user7860670


In this particular case, you can just define your null closure as one that always returns -1:

template <typename Lambda>
int foo(Lambda bar) {
    return bar(3);
}

#include <iostream>
int main() {
    auto const NULL_LAMBDA = [](int){ return -1; };
    std::cout << foo([](int a) {return a + 3;}) << std::endl;
    std::cout << foo(NULL_LAMBDA) << std::endl;
}

The likelihood is that if you're selecting at run-time which implementation to pass, then you're much better off type-erasing it with std::function rather than instantiating templates. And a std::function is permitted to be empty - it can be assigned from and compared against a null pointer.


If you know at compilation time that some call sites will always pass the 'null' lambda, then you can specialise the implementation appropriately. Obvious options include overloading foo() with a version that doesn't take the bar argument, or specializing it with a different implementation when bar is not a callable.

If much of foo() is common to both kinds of invocation (perhaps it has a lot of side effects, and bar() is provided as a callback?), then you might be able to conditionalise the optional part using std::is_same<>. This requires if constexpr, as the lambda is not callable as bar(3):

static auto const NULL_LAMBDA = nullptr;

#include <type_traits>
template <typename Lambda>
int foo(Lambda bar) {
    if constexpr (std::is_same<decltype(bar), std::nullptr_t>::value)
        return -1;
    else
        return bar(3);
}

#include <iostream>
int main() {
    std::cout << foo([](int a) {return a + 3;}) << std::endl;
    std::cout << foo(NULL_LAMBDA) << std::endl;
}
like image 9
Toby Speight Avatar answered Oct 18 '22 20:10

Toby Speight


Lambdas are a category of types, not a type.

We can do this:

struct null_callable_t{
  template<class...Ts>
  constexpr void operator()(Ts&&...)const{}
  explicit constexpr operator bool()const{return false;}
  constexpr null_callable_t() {}
  friend constexpr bool operator==(::std::nullptr_t, null_callable_t ){ return true; }
  friend constexpr bool operator==(null_callable_t, ::std::nullptr_t ){ return true; }
  friend constexpr bool operator!=(::std::nullptr_t, null_callable_t ){ return false; }
  friend constexpr bool operator!=(null_callable_t, ::std::nullptr_t ){ return false; }
};

constexpr null_callable_t null_callable{};

Now our code becomes:

template <typename Lambda>
int foo(Lambda bar) {
  if(!bar)
    return -1;
  else
    return bar(3);
}

which is pretty slick:

std::cout << foo([](int a) {return a + 3;}) << std::endl;
std::cout << foo(null_callable) << std::endl;

however, my personal favorite method to solve this is to write function_view.

It wraps a pointer and an action up in a non-allocating thing a bit like std function. Compilers are pretty good at inlining simple function pointers, so overhead remains low if you make the method inline.

like image 3
Yakk - Adam Nevraumont Avatar answered Oct 18 '22 20:10

Yakk - Adam Nevraumont