我目前正在开发一个用于函数抽象的库。我想将一个多参数函数抽象为一个参数函数,它是一个索引。我可以选择传递一个谓词对象,该对象确定它是否应该运行代码。
using FnType = std::function<void(const unsigned long)>;
template<typename Fn, typename Predicate, typename ... Args>
const FnType make_fn(const Fn &fn, const Predicate &predicate, Args ... args) {
return [&](const unsigned long idx) {
if(predicate(idx)) fn(idx, args ...);
};
}
template<typename Fn, typename ... Args>
const FnType make_fn(const Fn &fn, Args ... args) {
return [&](const unsigned long idx) {
fn(idx, args ...);
};
}
使用谓词时,此代码可以正常工作。
std::vector<double> 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 ...);
^~~~~~~~~
有没有可能说编译器应该使用不使用任何谓词的第二个函数?
答案 0 :(得分:5)
让我们来看看你的签名:
template<typename Fn, typename Predicate, typename ... Args>
const FnType make_fn(const Fn&, const Predicate&, Args ...); // (1)
template<typename Fn, typename ... Args>
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()
。
或者,我们可以通过强制Predicate
可以调用来改变SFINAE:
template <typename Fn, typename Predicate, typename... Args,
typename = decltype(std::declval<Predicate>()(0u))>
const FnType make_fn(const Fn&, const Predicate&, Args ...);
这会使(1)
对您的通话形成不良,因此(2)
会更受欢迎。虽然如果你不想以这种方式使用谓词,但另外还有一些函数,其第一个参数是谓词,那么这又有一个缺点。会发生错误的事情。
T.C.提出的更好的解决方案。在评论中只是引入一个空标签类型:
struct predicate_tag_t { };
constexpr predicate_tag_t predicate_tag{};
存在该标签时出现过载:
template <typename Fn, typename Predicate, typename... Args>
FnType make_fn(const Fn&, predicate_tag_t, const Predicate&, Args&&... );
template <typename Fn, typename... Args>
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
时,你有两个const FnType make_fn(const Fn &fn, Args ... args) {
return [&](const unsigned long idx) { ... }
}
的悬挂引用。
make_fn
您正在复制所有的args,然后保留对所有args的引用。但是args
完成后,所有[=]
都会超出范围。你需要复制它们 - 所以SQL> create or replace type tp_asset as object (assetId number, asset_name varchar2(100), asset_val number)
2 /
Type created
SQL> create or replace type tp_tab_asset is table of tp_asset;
2 /
Type created
SQL> create or replace function fnc_AssetAttributeByType(p_num in number) return tp_tab_asset pipelined as
2 v_tp_asset tp_asset;
3 begin
4 for i in 1 .. 3
5 loop
6 v_tp_asset := tp_asset(i, 'ABC', i * 3);
7 pipe row (v_tp_asset);
8 end loop;
9 return;
10 end;
11 /
Function created
SQL> select *
2 from table(fnc_AssetAttributeByType(3)) a
3 left join table(fnc_AssetAttributeByType(3)) b on b.assetid = a.assetid;
ASSETID ASSET_NAME ASSET_VAL ASSETID ASSET_NAME ASSET_VAL
---------- --------------- ---------- ---------- --------------- ----------
1 ABC 3 1 ABC 3
2 ABC 6 2 ABC 6
3 ABC 9 3 ABC 9
。
答案 1 :(得分:1)
将您的上层功能更改为
template<typename Fn, typename Predicate, typename ... Args
, typename = std::enable_if_t<sizeof ... (Args) != 0> >
const FnType make_fn(const Fn &fn, const Predicate &predicate, Args ... args) {
return [=](const unsigned long idx) {
if(predicate(idx)) fn(idx, args ...);
};
}
这确保参数包中至少有一个成员,然后在重载解析期间选择第二个函数。
还有一些事项需要注意:
最好使返回类型为auto
- 您仍然可以将其设置为std::function
,但如果只是调用它,则需要较少的开销。
您可能不希望按值Args ...
传递常规参数,因此最好写Args const& ... args
。