Detect if C++ lambda can be converted to function pointer

我们两清 提交于 2019-12-05 03:28:17

If you know the signature of a function you want your lambda to be converted to, you can leverage the std::is_assignable trait:

auto lambda = [] (char, double) -> int { return 0; };
using signature = int(char, double);
static_assert(std::is_assignable<signature*&, decltype(lambda)>::value, "!");

DEMO

This way it can work also with generic lambdas.


I'd like to have a solution that works for vs 2013/2015, gcc and clang

If you don't know the signature, here's an approach that is an alternative to checking whether + triggers an implicit conversion. This one exploits the std::is_assignable test and verifies whether a lambda is assignable to a function pointer with the same signature as the lambda's function call operator. (Just like a test with unary operator plus, this doesn't work with generic lambdas. But in C++11 there are no generic lambdas).

#include <type_traits>

template <typename T>
struct identity { using type = T; };

template <typename...>
using void_t = void;

template <typename F>
struct call_operator;

template <typename C, typename R, typename... A>
struct call_operator<R(C::*)(A...)> : identity<R(A...)> {};

template <typename C, typename R, typename... A>
struct call_operator<R(C::*)(A...) const> : identity<R(A...)> {};

template <typename F>
using call_operator_t = typename call_operator<F>::type;

template <typename, typename = void_t<>>
struct is_convertible_to_function
    : std::false_type {};

template <typename L>
struct is_convertible_to_function<L, void_t<decltype(&L::operator())>>
    : std::is_assignable<call_operator_t<decltype(&L::operator())>*&, L> {};

Test:

int main()
{
    auto x = [] { return 5; };
    auto y = [x] { return x(); };

    static_assert(is_convertible_to_function<decltype(x)>::value, "!");
    static_assert(!is_convertible_to_function<decltype(y)>::value, "!");
}

GCC, VC++, Clang++

The HasConversion approach you have going is a holdover from C++03. The idea there was to use the different return types of the overloads of test (have one return a char and the other a long, for instance) and check that the sizeof() of the return type matches what you expect.

Once we're on C++11 though, there are much better methods. We can, for instance, use void_t:

template <typename... >
using void_t = void;

to write a type trait:

template <typename T, typename = void>
struct has_operator_plus : std::false_type { };

template <typename T>
struct has_operator_plus<T, 
    void_t<decltype(+std::declval<T>())>>
: std::true_type { };

int main() {
    auto x = []{ return 5; };
    auto y = [x]{ return x(); };

    std::cout << has_operator_plus<decltype(x)>::value << std::endl; // 1
    std::cout << has_operator_plus<decltype(y)>::value << std::endl; // 0
}

Non-capturing lambdas have a very interesting property : they can convert to an adequate function pointer, but they can also do so implicitly when you apply unary operator + to them. Thus :

template<class...> using void_t = void;

template <class T, class = void>
struct has_capture : std::true_type {};

template <class T>
struct has_capture<T, void_t<decltype(+std::declval<T>())>> : std::false_type {};

int main() {
    auto f1 = []{};
    auto f2 = [&f1]{};

    static_assert(!has_capture<decltype(f1)>{}, "");
    static_assert( has_capture<decltype(f2)>{}, "");
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!