将带有默认参数值的函数作为模板参数传递

8
我对以下行为感到有些困惑。我将一个带有默认值的参数和另一个参数一起传递给函数作为模板参数,并只调用函数一次。为什么会出现编译错误?如何解决或规避这个问题?
#include <iostream>
using namespace std;

template<typename Function> 
void eval(Function function) {
    function(10);
}

void sum(int i, int j = 0) {
    cout << "Sum is " << i + j;
}

int main() {
    sum(10);    // OK, of course :)
    eval(sum);  // Error!
}

请注意,这个问题与使用默认参数调用模板函数无关。 错误信息:
prog.cpp: In instantiation of 'void eval(Function) [with Function = void (*)(int, int)]':
prog.cpp:15:10:   required from here
prog.cpp:6:10: error: too few arguments to function
  function(10);
          ^

请提供错误信息,这会有所帮助。 - Bernhard
1
@perreal 不是!那是关于带有默认参数的模板函数。我的问题是关于将带有默认参数的函数作为模板参数传递给另一个函数。 - Eissa N.
@EissaN,你指的是“函数”,而不是“模板函数”。sum不是一个模板。 - Aaron McDaid
3个回答

5
这是因为可选参数是函数声明的一部分。当您使用函数指针调用时,编译器实际上只知道函数指针的类型。因此,这行代码 function(10) 大致可以翻译为:
void (*)(int, int) sm = function; // the equivalent happens at the type deduction step 
sm(10); // whoops, where is the second int

编译器需要第二个参数,因为它无法知道 sm 是指向具有默认参数的 sum,还是指向其他没有默认参数的 void foo(int a, int b)

4
这就是函数指针的弱点所在。你已经得到了两个很好的答案,解释了为什么这不起作用 - 函数模板不知道你的函数有默认参数。
解决方案是不要传递函数指针 - 传递一个函数对象。在这种情况下,你可以手写一个 lambda 表达式:
 eval([](int i){ return sum(i); });

这似乎是一种非常烦人的冗长方式,基本上只是以实际有效的方式编写sum。但是这种情况会定期出现(如果sum是一个重载的名称怎么办?),因此拥有一个lambda宏很方便(需要C++14):

#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(auto) { \
   return func(std::forward<decltype(args)>(args)...); }

这样你就可以编写:

eval(AS_LAMBDA(sum));

这将适用于默认参数、重载名称,甚至可以绑定成员函数:

struct X {
    int i;
    int add(int j) { return i + j; }
};

X x{42};
eval(AS_LAMBDA(x.add));

2
当您调用 sum 函数时:
sum(10);

编译器将其翻译为:
sum(10, 0);

没有错误,因为参数相匹配。对于编译器来说,不存在只有一个参数的sum。你可以说是由一个预编译器来实现将0作为第二个参数传递给sum。在使用模板时,这种神奇的预编译器不存在,实际的编译器会将sum和严格两个参数进行匹配,但找不到这样的函数,因此就会出错。
就像...
void foo(int);
void foo(float);

如果参数double可以更改为floatint,那么使用foo(10.2);将无法编译通过。编译器不会自动假设使用float。同样地,编译器也不会仅为了编译成功而尝试寻找最佳匹配的sum


5
抛开"魔法"不谈,更准确地说,默认参数不是函数类型定义的一部分,这就是模板所能看到的全部。 - StoryTeller - Unslander Monica
有没有什么方法可以解决这个问题?我认为在C++14中,你可以将sum封装在一个可变参数的lambda函数中。例如:eval( [](auto...args){ return sum(std::forward<decltype(args)>(args)...);}); - Aaron McDaid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接