2

The standard defines closure types as follow:

[expr.prim.lambda.closure] The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type, called the closure type, whose properties are described below. [...]

Would there be a way to create a type trait to detect closure types:

template <class T> 
struct is_closure {
    static constexpr bool value = /* something */
};

template <class T>
inline constexpr bool is_closure_v = is_closure<T>::value;

If not doable in purely standard C++, intrinsics working on clang++-5.X and g++-7.X are welcome.

EDIT: Wouldn't it be doable to get the name of the lambda type using some compiler macros and then do some constexpr string processing?

Vincent
  • 57,703
  • 61
  • 205
  • 388
  • And you want it distinguished from other *"unnamed non-union class types"* I imagine? – StoryTeller - Unslander Monica Apr 06 '18 at 11:38
  • 3
    Really strange requirement... Could you provide some context ? – llllllllll Apr 06 '18 at 11:40
  • 1
    Seems impossible https://stackoverflow.com/questions/8481997/how-to-detect-whether-a-type-is-a-lambda-expression-at-compile-time – llllllllll Apr 06 '18 at 11:42
  • @StoryTeller: Yes – Vincent Apr 06 '18 at 11:44
  • 2
    Context: https://github.com/vreverdy/type-utilities/blob/master/doc/src/p1016r0.pdf – Vincent Apr 06 '18 at 11:46
  • lambdas are specified to be equivalent to a function object, so there's no way to tell the difference between them and other function objects AFAIK – AndyG Apr 06 '18 at 11:50
  • 3
    *Why* do you need this? – Jesper Juhl Apr 06 '18 at 11:51
  • 1
    If it were part of the standard, I'm sure that implementors could do some additional tagging so that this would be possible, but I think it will be difficult to make an argument in favor of the type trait. – AndyG Apr 06 '18 at 11:54
  • 1
    @AndyG: in the C++ standard vocabulary, pointers to functions are function objects. And for your question: yes I would just like to be able to have more information on callable types than currently. – Vincent Apr 06 '18 at 11:54
  • 1
    @Vincent: You are misreading [function.objects]. It refers to any type that can be used in a place where you could use a pointer to a function. (basically meaning anything that you can call like a function). Also see [cppreference](http://en.cppreference.com/w/cpp/utility/functional) – AndyG Apr 06 '18 at 11:59
  • @AndyG: https://stackoverflow.com/questions/49503229/are-function-pointers-function-objects-in-c – Vincent Apr 06 '18 at 12:43
  • 1
    @Vincent: Yes, function pointers are function objects. But they are not the only thing that is a function object. A lambda is a function object, for example. – AndyG Apr 06 '18 at 12:45
  • I see no way in stock (standard) C++ to tell the difference of `struct foo { int i; foo(int i) : i(i) {} void operator(){ std::cout << i; }};` and `[i](){ std::cout << i; };` – NathanOliver Apr 06 '18 at 12:51
  • @Vincent: "*yes I would just like to be able to have more information on callable types than currently.*" The question is *why* you need to know. Show us the code that would need to execute differently based on this detection. There is no reason to write a function that excludes non-lambdas or behaves differently with lambdas. – Nicol Bolas Apr 06 '18 at 15:21

1 Answers1

7

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Some false positives currently: [Demo](http://coliru.stacked-crooked.com/a/0bf433639cd89148) ([pretty function](http://coliru.stacked-crooked.com/a/b3816414f5f040bb)). Some more parsing would be required. – Jarod42 Apr 07 '18 at 03:20
  • @Jarod42 Nice catch! For now I'll just edit into the answer that it's wrong :-) – Barry Apr 07 '18 at 17:09