Home > Blockchain >  Why does each instantiation of a lambda function result in a unique type?
Why does each instantiation of a lambda function result in a unique type?

Time:06-08

Recently I asked a question about some code I had which was causing my compiler to run out of heap space. This code contained a recursive template function which was being passed a lambda function. Each instantiation of the template function caused (via the recursive call) another instantiation of the same template function with it's lambda function parameter. It was explained that each instantiation of the lambda function results in a unique type, so an infinite loop of instantiations results and the compiler runs out of heap. Note that the logic of the program is such that there would be no infinite loop at runtime.

Here's an example of the code, this code does not compile on any of the major compilers. This code is a heavily cut down version of my real code, it's just for illustrative purposes.

struct Null
{
    template <typename SK>
    static int match(int n, SK sk)
    {
        return 0;
    }
};

template <typename T>
struct Repetition
{
    template <typename SK>
    static int match(int n, SK sk)
    {
        return T::match(0, [n]() { return match(0, [n](){ return n; }); });
    }
};

int main()
{
    using Test = Repetition<Null>;
    Test::match(0, [](){ return 0; });
}

When this was explained it occurred to me that I could break the infinite loop by using a functor instead of a lambda function, since each instantiation of a the functor would not be a unique type. Here's that code, this compiles fine

struct Null
{
    template <typename SK>
    static int match(int n, SK sk)
    {
        return 0;
    }
};

template <typename T>
struct Repetition
{
    template <typename SK>
    static int match(int n, SK sk)
    {
        return T::match(0, [n]() { return match(0, RepetitionSK<T>(n)); });
    }
};

template <typename T>
struct RepetitionSK
{
    RepetitionSK(int n) : n(n) {}
    int operator()() { return n; }
    int n;
};

int main()
{
    using Test = Repetition<Null>;
    Test::match(0, [](){ return 0; });
}

EDIT [=] captures have been replaced by the more explicit [n]

My question is that why do lambda functions behave this way? The lambda functions in Repetition::match do not depend the template parameter SK in any way. What's the benefit in having each instantiation of a lambda function result in a unique type?

Another question, is it possible to write this code using only lambda functions, no functors or other auxiliary classes?

CodePudding user response:

The question is more easily answered by considering the opposite. If it were not true, then some lambda's would have the same type. Furthermore, the Standard should then say which lambda's have the same type, and which lambda's have distinct types.

Note that with "type" we mean more than just the signature of operator(). Lambda's have real type, e.g. they've got a sizeof (which has to include the captured state).

For that reason, you can't just define the type in terms of tokens; the token T may be the same but the meaning varies across instantiations.

This idea isn't entirely unique; non-inline functions are also unique even if they contain the exact same tokens (but then the function type will be the same - the correspondence isn't exact. And functions don't have a sizeof)

  • Related