Home > Enterprise >  Why std::integral_constant<lambda> doesn't work the same way as std::integral_constant<
Why std::integral_constant<lambda> doesn't work the same way as std::integral_constant<

Time:02-08

(related to this question).

I am trying to combine unique_ptr and lambda via std::integral_constant (taking address of most std functions is outlawed in C 20, I am figuring out a convenient way to wrap them in lambda). I noticed weird behaviour of std::integral_constant that I can't explain (godbolt):

#include <type_traits>

template<auto L, class T = decltype(L)>
using constant = std::integral_constant<T, L>;

void dummy(void*);

int main() 
{
    using C1 = constant<&dummy>;
    using C2 = constant<[](void* p){ dummy(p); }>;

    C1()()(nullptr);        // #1 works as expected
    C2()()(nullptr);        // #2 works as expected

    C1()(nullptr);          // #3 unexpectedly works
    C2()(nullptr);          // #4 fails as expected

    return 0;
}

Can someone explain why line #3 compiles? This is what std::unique_ptr uses under cover (when you use std::integral_constant as deleter) and this is why my attempts to use lambda instead of function address fail.

P.S. Line #4 fails with following message:

<source>: In function 'int main()':
<source>:17:17: error: no match for call to '(C2 {aka std::integral_constant<const main()::<lambda(void*)>, <lambda closure object>main()::<lambda(void*)>{}>}) (std::nullptr_t)'
   17 |     C2()(nullptr);          // #4 fails as expected

CodePudding user response:

When performing a function call on an object, not only the call operators are considered. There is a special exception if a non-explicit conversion function to a function pointer type (or function reference type) with suitable cvref-qualifiers exists.

In these situations [over.call.object]/2 says that an additional overload is generated, a surrogate call function which takes the converted implicit object pointer as first argument and the function pointer/reference's parameters as further parameters. If this overload is chosen, it will use the conversion function to convert this to the function pointer/reference and then call it with the remaining provided arguments.

std::integral_constant has a non-explicit conversion function to value_type and so if value_type is a function pointer/reference, and only then, will this surrogate call exist, which essentially forwards the object function call to a call to the stored function pointer/reference.

  •  Tags:  
  • Related