【问题标题】:Lambda as template functionLambda 作为模板函数
【发布时间】:2016-05-14 20:38:44
【问题描述】:

我有一个很奇怪的问题。为了简单起见,假设我想要一个函数,它需要两个函数,其声明与参数相同

template<typename Func>
void foo(Func a, Func b)
{
    std::cout << "good";
}

为了尝试一下,我从 cstdio 中提取了 putchar,并创建了一个相同的函数来匹配 putchar

int myPutcharFunc(int)
{
    return 0;
}

int main()
{
    auto myPutcharLambda = [](int) -> int
    {
        return 0;
    };

    foo(putchar, myPutcharFunc); // okay
    foo(putchar, myPutcharLambda);  //deduced conflicting types for parameter 'Func' ('int (__attribute__((__cdecl__)) *)(int)' and 'main()::<lambda(int)>')
}

现在,lambda 不想编译(关键是我想使用 lambda 捕获)。

所以让我们添加模板专业化,因为程序员比机器更聪明,对吧? :)

template<typename Func>
void foo(Func a, Func b)
{
    std::cout << "good";
}

template<>
void foo(int(*)(int), int(*)(int))
{
    std::cout << "good";
}

不走运,同样的错误 - 为什么? 但是由于某种原因,当我注释掉模板专业化时:

//template<>
void foo(int(*)(int), int(*)(int))
{
    std::cout << "good";
}

代码编译。我显然不想为每组函数的参数重载foo——这就是模板的用途。每个步骤都使用 msvc++ 和 g++ 进行了测试。我做错了什么?

【问题讨论】:

    标签: c++ templates c++11 lambda


    【解决方案1】:

    两种可能性。

    1:只需将 + 放在 lambda 前面即可:

    foo(putchar, +myPutcharLambda);
    

    这很有效,因为一元 + 需要一个类似整数的值,例如指针。因此,lambda 转换为函数指针。

    最终(非捕获)lambda 与函数指针的类型不同,即使它愿意转换为函数指针。

    编译器应该如何知道允许哪些转换来生成相同类型的两个对象?

    2:还有另一种选择,利用?:愿意进行一些转换的事实,converting one type to another in some circumstances

    template<typename Func1, typename Func2>
    void foo2(Func1 a, Func2 b)
    {
        using common_type = decltype(true?a:b); // or 'false', it doesn't matter
        foo<common_type>(a,b);
    }
    

    【讨论】:

    • 选项 1 特别顽皮。 :)
    【解决方案2】:

    每个 lambda 都是不同的类型,因此您需要有两个不同的模板参数来获取它们

    template<typename FuncA, typename FuncB>
    void foo(FuncA a, FuncB b)
    

    推断模板类型时,类型不会衰减(请参阅评论以获得更正)。所以 lambda 仍然是 lambda 并且不会衰减为函数指针。同样的原因,字符串文字被推断为 char[N] 而不是 const char *

    在您的第二个示例使用特化时,它不想使用您的特化,因为 lambda 不是函数指针。您可以将 Lambda 转换为函数指针并使其工作:https://godbolt.org/g/ISgPci 您可以在此处执行的 trick 是说 +my_lambda 因为 + 是为指针定义的,因此它将强制非捕获 lambda变成函数指针。

    【讨论】:

    • “推导模板类型时类型不会衰减”当然会。 “与将字符串文字推导出为 char[N] 而不是 const char * 的原因相同”,将原始字符串文字推导为 const char*
    • @PiotrSkotnicki 那么它什么时候以字符数组的形式出现呢?我只是在其他方面读到了这一点。
    • 当函数参数是引用类型时
    【解决方案3】:

    lambda 有自己的类型,它可以衰减为函数指针,但在模板函数匹配的情况下不会,因为隐式转换,它将用于您找到的真实函数。

    在匹配模板的情况下,您需要消除歧义并使用所需类型显式实例化 foo 或将 lambda 转换为函数指针。

    foo<decltype(putchar)>(putchar, myPutcharLambda);
    

    foo(putchar, +myPutcharLambda);
    

    【讨论】: