我目前正在开发一个用于函数抽象的库.我想将一个多参数函数抽象为一个参数函数,它是一个索引.我可以选择传递一个谓词对象,该对象确定它是否应该运行代码.
using FnType = std::function;
template
const FnType make_fn(const Fn &fn, const Predicate &predicate, Args ... args) {
return [&](const unsigned long idx) {
if(predicate(idx)) fn(idx, args ...);
};
}
template
const FnType make_fn(const Fn &fn, Args ... args) {
return [&](const unsigned long idx) {
fn(idx, args ...);
};
}
使用谓词时,此代码可以正常工作.
std::vector data(10);
auto fn = [&](unsigned idx, double d) { data[idx] *= d; };
auto pred = [](unsigned long idx) { return idx % 2 == 0; };
FnType new_fn = make_fn(fn, pred, 2.0);
但是当我试图传递没有谓词时
FnType new_fn2 = make_fn(fn, 2.0);
它返回以下编译时错误消息:
called object type 'double' is not a function or function pointer
if(predicate(idx)) fn(idx, args ...);
^~~~~~~~~
是否有可能说编译器应该使用不使用任何谓词的第二个函数?
我们来看看你的签名:
templateconst FnType make_fn(const Fn&, const Predicate&, Args ...); // (1) template const FnType make_fn(const Fn&, Args ... ) // (2)
当我们打电话时:
make_fn(fn, 2.0);
没有什么特别的Predicate
- 它只是另一种类型.因此,当我们执行重载决策时,我们有两个完全匹配.我们解决可变参数的方式(1)
是比它更专业(2)
- 因为(1)
至少需要2个参数并(2)
至少需要1个参数,前者更具体.这就是我们称之为前者的原因.编译器不会从签名中知道您的意思Predicate
.
最简单的解决方案是简单地重命名一个函数或另一个函数 - 你真的需要它们重载吗?有一个make_fn()
和一个make_predicate_fn()
.
或者,我们可以通过强制执行可更改来改变SFINAE Predicate
:
template()(0u))> const FnType make_fn(const Fn&, const Predicate&, Args ...);
那会使(1)
你的电话形成不良,所以(2)
会更受欢迎.虽然如果你不想以这种方式使用谓词,但另外还有一些函数,其第一个参数是谓词,那么这又有一个缺点.会发生错误的事情.
TC在评论中提出的更好的解决方案是引入一个空标签类型:
struct predicate_tag_t { }; constexpr predicate_tag_t predicate_tag{};
并且存在该标记时出现过载:
templateFnType make_fn(const Fn&, predicate_tag_t, const Predicate&, Args&&... ); template FnType make_fn(const Fn&, Args&&... );
那样你的例子变成了:
FnType new_fn = make_fn(fn, predicate_tag, pred, 2.0); FnType new_fn2 = make_fn(fn, 2.0);
make_fn
当你有:时,你有两个s中的悬空引用:
const FnType make_fn(const Fn &fn, Args ... args) { return [&](const unsigned long idx) { ... } }
您正在复制所有的args,然后保留对所有args的引用.但是一旦make_fn
完成,所有的args
都超出了范围.你需要复制它们 - 所以[=]
.