非捕获 lambda 不会降级为函数指针

Mus*_*ang 0 c++ lambda templates

下面是一个典型的 C 函数,它采用经典函数指针作为参数:

int DoThingy( const char* stuff, int(*helper)(int))
{
    int result = 0;
    //...call helper...
    return result;
}
Run Code Online (Sandbox Code Playgroud)

下面我用一个非捕获 lambda 来调用上面的内容,它神奇地“降级”为函数指针。在“A”处,转换是隐式的。在“B”处,它是明确的。都好。

void UseThingy()
{
    auto lam = [](int)->int {
        return 42;
    };

    int i;
    i = DoThingy( "hello", lam); //"A" works

    int (*ptr)(int) = lam;
    i = DoThingy( "hello", ptr); //"B" works
}
Run Code Online (Sandbox Code Playgroud)

但在这个新示例中,回调函数的签名取决于模板类型:

template <typename T>
int DoThingy2( const char* stuff, int (*helper)(T))
{
    int result = 0;
    //...call helper...
    return result;
}
Run Code Online (Sandbox Code Playgroud)

当我尝试使用上面的这个版本时,“C”行甚至无法编译。然而“D”处的显式版本是有效的。什么?为什么这些不一样?请注意,当我在“E”处给出显式模板参数时,它可以工作,但当然可以<int>从 的签名推断出来lam,对吧?

void UseThingy2()
{
    auto lam = [](int)->int {
        return 42;
    };
    
    int i;
    
    i = DoThingy2( "hello", lam); //"C" won't compile

    int (*ptr)(int) = lam;
    i = DoThingy2( "hello", ptr); //"D" works

    i = DoThingy2<int>( "hello", lam); //"E" works
}
Run Code Online (Sandbox Code Playgroud)

以下内容也无法编译,我希望它能编译:

    i = DoThingy2( "hello", [](int)->int {return 42;}); //"F" won't compile
Run Code Online (Sandbox Code Playgroud)

我之前应该说的是,我需要将函数参数设置为默认值,以便在调用者未提供任何参数时使用0内部版本。helper

请注意,这个问题与提议的重复问题明显不同。特别是关于可选参数,此外它还使用 C++17 模板选择功能提供了一组更好的答案。

Nat*_*ica 6

您的问题是模板参数推导。当该进程运行时,不会发生任何转换,编译器获取提供的对象,获取它的类型,然后尝试从此类型推导出模板参数

在这种情况下,当函数lamba_object_from_main需要函数指针时,它会获取(组成类型名称)。它无法推断,T因为lamba_object_from_main这是它唯一需要处理的东西。

您可以更改函数以仅接受第二个参数的任何内容,而不是使用函数指针,例如

template <typename Func>
int DoThingy( const char* stuff, Func helper)
{
    int result = 0;
    //...call helper...
    return result;
}
Run Code Online (Sandbox Code Playgroud)

如果您想确保helper具有特定的返回类型和参数,您可以限制模板,例如

template <typename Func, std::enable_if_t<is_invocable_r_v<int, Func, int>, bool> = true>
int DoThingy( const char* stuff, Func helper)
{
    int result = 0;
    //...call helper...
    return result;
}
Run Code Online (Sandbox Code Playgroud)

如果您希望该功能是可选的,那么至少有几个选项。一是把模板改成

template <typename Func = int(*)(int),
          std::enable_if_t<std::is_invocable_r_v<int, Func, int>, bool> = true>
int DoThingy2( const char*, Func helper = [](int i){ return internal_function(i); })
{
    int result = 0;
    helper(result);
    return result;
}
Run Code Online (Sandbox Code Playgroud)

它使用默认的 lambda 来调用默认的内部函数

另一种选择是有两个重载,一个重载接受函数,另一个重载不接受函数,但调用与默认函数一起执行的重载,例如

template <typename Func, std::enable_if_t<is_invocable_r_v<int, Func, int>, bool> = true>
int DoThingy( const char* stuff, Func helper)
{
    int result = 0;
    //...call helper...
    return result;
}

int DoThingy( const char* stuff)
{
    return DoThingy(stuff, internal_function);
}
Run Code Online (Sandbox Code Playgroud)