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?
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;
}
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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With