I would love to see if there was actually a motivating use-case for needing to know, specifically, whether a type was a closure or not. I'm not sure if there is even a use-case for knowing if, in general a type can be invoked with some arguments - as opposed to specifically being invocable with a particular set of them. This kind of strikes me as intellectual masturbation.
That said, I love intellectual masturbation! The nice thing about closure types is that they're not nameable. But we still have hooks like __PRETTY_FUNCTION__
that give us the string form of names, so they have to be printable somehow -
and that somehow must be differentiable from normal names. Let's find out how:
#include <iostream>
template <typename T>
void print(T) {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
namespace N {
void foo() {
print([]{ return 1; });
}
}
int main() {
print([]{ return 1; });
print([](auto, auto&&...){ return 2; });
N::foo();
}
gcc prints:
void print(T) [with T = main()::<lambda()>]
void print(T) [with T = main()::<lambda(auto:1, auto:2&&, ...)>]
void print(T) [with T = N::foo()::<lambda()>]
clang prints:
void print(T) [T = (lambda at bar.cxx:15:11)]
void print(T) [T = (lambda at bar.cxx:16:11)]
void print(T) [T = (lambda at bar.cxx:10:11)]
In neither case are these strings producible from non-closure types. We cannot have types that start with <
(gcc), and we cannot have types with spaces like lambda at
(clang). So we can take advantage of that by building a type trait that just looks for the appropriate substring:
constexpr bool is_equal(const char* a, const char* b) {
for (; *a && *b; ++a, ++b) {
if (*a != *b) return false;
}
return *b == '\0';
}
constexpr bool has_substr(const char* haystack, const char* needle) {
for(; *haystack; ++haystack) {
if (is_equal(haystack, needle)) {
return true;
}
}
return false;
}
template <typename T>
constexpr bool closure_check() {
return has_substr(__PRETTY_FUNCTION__,
#ifdef __clang__
"(lambda at"
#else
"::<lambda"
#endif
);
}
template <typename T>
constexpr bool is_closure_v = closure_check<T>();
template <typename T>
struct is_closure
: std::integral_constant<bool, is_closure_v<T>>
{ };
I haven't tested this thoroughly, so I cannot say with confidence that it has neither false positives nor negatives, but it seems like the right approach here.
Edit: As Jarod42 points out, this is currently insufficient, as it fails to take into account, at the very least, closure types as template parameters: demo. There may be other false positives along this line.
Though, to reiterate, I am not clear on why such a thing might be useful.