On Saturday, May 17, 2025, Oliver Hunt wrote:

How would this work?

You’re wanting to call a function, but calling a function requires knowing the type of that function, but you cannot know who is calling you yet because you haven’t finished compiling the function, let alone the functions that might call it.


I forgot my laptop today so I'm doing this on my phone, and I'm a tad tired from running a half-marathon in 2h 7m (PB = 1hr 43m), but hopefully this works out okay...

First of all I want you to consider the following program:

    unsigned a(void) { return 6u; }

    unsigned b(void) { return a(); }

    unsigned c(void) { return b(); }

    int main()
    {
        return c();
    }

When control reaches 'a', if you stop the debugger and do a stack trace, then you will see that main called c, c called b, and b called a. (But make sure you turn off all optimisations otherwise 'main' might just get compiled to two instructions: "mov rax, 0x6" followed by "ret").

In my original post that had the example of:

    std::recurse< -1 >( args );

Here I was not at all talking about the function call stack. I was not talking about what function invokes what function. I was talking about something very different. I was talking about functions being defined inside other functions. This was not possible up until C++ 11, when lambdas were introduced.

I want to make a change to my original idea here. Rather than invoking "std::recurse" to perform the invocation, instead I want it to return a function pointer, so I'll rename it to "std::addr_func" which is a consteval template function defined as follows:

    template<unsigned n = 0u>
    consteval auto addr_func(void) noexcept
    {
        // This function returns a function pointer
        return __builtin_addr_func(n);
    }

If you invoke "std::addr_func" inside a function which is not a lambda function, then the template parameter 'n' must always be zero, and the return value will always be a pointer to the current function. You can use 'decltype' on this pointer to figure out the function's return type and parameter types, as described here:

    https://devblogs.microsoft.com/oldnewthing/20200713-00/?p=103978
    (I have no affiliation with Microsoft by the way)

Things get interesting though when one function is defined inside another function:

unsigned Func(unsigned arg)
{   
    return [](unsigned arg)
    {
        return [](unsigned arg)
        {
            return  [](unsigned arg)
            {
                if ( arg % 8u ) return std::addr_func<2u>()( ++arg );
                return 4u + arg;
            }(arg);
        }(arg);      
    }(arg);
}

int main(void)
{
    return Func(2u);
}

In the innermost lambda which contains the statement, "return 4u + arg;", you can invoke "std::addr_func" four different ways:

    std::addr_func<0u>();  -- innermost lambda
    std::addr_func<1u>();  -- middle lambda
    std::addr_func<2u>();  -- outermost lambda
    std::addr_func<3u>();  -- Func

But as you correctly pointed out: When the compiler is processing the characters of the second lambda, the first lambda isn't finished being written yet, so the compiler can't possibly know the return type of the first lambda at this point. So there is a caveat here:

    std::addr_func can only be used to get the address of a function whose return type isn't 'auto' or 'decltype(auto)'. So you'd need to tweak the outermost lambda as follows:

unsigned Func(unsigned arg)
{
    return [](unsigned arg) -> unsigned
    {
        return [](unsigned arg)
        {
            return  [](unsigned arg)
            {
                if ( arg % 8u ) return std::addr_func<2u>()( ++arg );
                return 4u + arg;
            }(arg);
        }(arg);      
    }(arg);
}

But there's something more to consider. What if the function is a member function? What if it's something like:

unsigned MyClass::Func(unsigned arg)
{   
    return [](unsigned arg)
    {
        return [](unsigned arg)
        {
            return [](unsigned arg)
            {
                if ( arg % 8u ) return std::addr_func<3u>()( ++arg );
                return 4u + arg;
            }(arg);
        }(arg);      
    }(arg);
}

Well . . . it would be wonderful and jolly just to say that 'std::addr_func<3u>()' will simply return a member function pointer instead of a normal function pointer, but then the next question would be "but... em.... how do you invoke it without a 'this' pointer"? Well here's one possible solution, and try not to vomit a few centilitres of your last decaffeinated coffee into your mouth:

unsigned MyClass::Func(unsigned arg)
{
    static thread_local MyClass *pthis = nullptr;
    pthis = this;

    return [](unsigned arg)
    {
        return [](unsigned arg)
        {
            return [](unsigned arg)
            {
                if ( arg % 8u ) return pthis->(*std::addr_func<3u>())( ++arg );
                return 4u + arg;
            }(arg);
        }(arg);      
    }(arg);
}

Not sure how well that would work out. And furthermore, if the lambdas have captures then the lambda's addresses would also have to be a 'member function pointer' instead of a 'function pointer'. We could figure this out with thread_local variables as I did above, but then we would also need another new function inside the standard library:

    std::this_pointer();

If you invoke the above function inside a lambda (or inside any member function for that matter), it gives you the lambda's 'this' pointer. (Note that it does not give you the captured 'this' inside a lambda -- rather it gives you the actual lambda object's 'this' pointer).

I admit that I'm making this up as I go along. Sometimes half-baked spontaneous idea sharing is good though.