Seokmin Hong
Seokmin Hong

Reputation: 622

How Can I use Null Lambda in C++?

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?

Upvotes: 20

Views: 9657

Answers (4)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275740

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.

Upvotes: 3

John Jones
John Jones

Reputation: 41

Note in C++17 we can write this like...

template<typename Lambda>
int foo(Lambda bar)
{
    if constexpr (std::is_same_v<std::decay_t<Lambda>, std::nullptr_t>)
        return -1;
    else if constexpr (std::is_convertable_v<Lambda, bool>)
    {
        if (bar)
            return bar(3);
        else
            return -1;
    }
    else
        return bar(3);
}

I suppose in C++20 we can define a concept that std::invocable (concepts header) or nulltpr_t to constrain Lambda by.

Upvotes: 1

Toby Speight
Toby Speight

Reputation: 30873

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;
}

Upvotes: 9

user7860670
user7860670

Reputation: 37600

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;
}

Upvotes: 28

Related Questions