C ++使用可变参数模板编译时间特定的函数

时间:2016-01-07 19:48:22

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

我目前正在开发一个用于函数抽象的库。我想将一个多参数函数抽象为一个参数函数,它是一个索引。我可以选择传递一个谓词对象,该对象确定它是否应该运行代码。

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 ...);
       ^~~~~~~~~

有没有可能说编译器应该使用不使用任何谓词的第二个函数?

2 个答案:

答案 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