【问题标题】:Most terse and reusable way of wrapping template or overloaded functions in function objects在函数对象中包装模板或重载函数的最简洁和可重用的方式
【发布时间】:2021-04-24 22:42:39
【问题描述】:

场景一:模板函数pred

template<typename T>
bool pred(T t) { /* return a bool based on t */ }

场景2:一组同名pred重载的函数

bool pred(A t) { /* return a bool based on t */ }
bool pred(B t) { /* return a bool based on t */ }
bool pred(C t) { /* return a bool based on t */ }
...

无论我们处于两种情况中的哪一种,最重要的是pred 不引用函数,因此它不能被传递,例如作为std::remove_if 的一元谓词。

因此,在这种情况下,可以方便地定义以下可以传递的对象,

auto constexpr predObj = [](auto t){ return pred(t); };

但是,一旦我对另一个一元谓词有类似的需求,我需要复制并粘贴该行并将两个名称更改为其他名称;同样,如果我需要对二元谓词这样做:

auto contexpr binPredObj = [](auto x, auto y){ return binPred(x, y); };

有没有一种自动制作的通用方法?我正在考虑类似的事情

auto funObj = fun2Obj(fun);

我觉得我所问的完全是不可能的,因为它需要传递 fun,因为它是一个函数对象,但事实并非如此,否则我不需要用它制作一个函数对象.但问绝不是犯罪,对吧?

【问题讨论】:

    标签: c++ overloading overload-resolution function-templates function-object


    【解决方案1】:

    一段时间后,我与一位同事就这个话题进行了交谈,他让我知道有一个 Boost 库通过两个名为 BOOST_HOF_LIFT and BOOST_HOF_LIFT_CLASS 的宏提供了这种功能。

    例子:

    #include <boost/hof.hpp>
    #include <cassert>
    #include <algorithm>
    
    // Declare the class `max_f`
    BOOST_HOF_LIFT_CLASS(max_f, std::max);
    
    int main() {
        auto my_max = BOOST_HOF_LIFT(std::max);
        assert(my_max(3, 4) == std::max(3, 4));
        assert(max_f()(3, 4) == std::max(3, 4));
    }
    

    另外,here 是一篇关于该主题的不错的博客文章,它也指向了 Yakk - Adam Nevraumont 的回答所指向的相同提议。


    我的问题是关于模板化和/或重载的函数,因为这是你不能传递的“命名事物”的“类别”。

    但是,还有另一个类别:构造函数。例如,您不能将构造函数作为运算符传递给std::transform 算法。

    嗯,Boost.Hof 在这方面也有帮助,通过 boost::hof::construct 模板,assert(construct&lt;T&gt;()(xs...) == T(xs...)); 成立。

    可以这样使用

    #include <boost/hof/construct.hpp>
    
    struct A {
      A(int) {}      // some...
      A(int, int) {} // ... constructors
    };
    
    auto makeA = boost::hof::construct<A>();
    
    // now `makeA` is the "objectified" version of `A`, so you could use it like this:
    std::transform(vecOfInts.begin(), vecOfInts.end(), vecOfAs.begin(), makeA);`
    

    【讨论】:

      【解决方案2】:

      这是一个执行正确 SFINAE、noexcept 和转发的宏:

      #define RETURNS(...) \
        noexcept(noexcept(__VA_ARGS__)) \
        -> decltype(__VA_ARGS__) \
        { return __VA_ARGS__; }
      
      #define CALLER_OF(...) \
        [](auto&&...args) \
        RETURNS( __VA_ARGS__(std::forward<decltype(args)>(args)...)) )
      
      #define CALLER_OF_WITH_CAPTURE(...) \
        [&](auto&&...args) \
        RETURNS( __VA_ARGS__(std::forward<decltype(args)>(args)...)) )
      
      auto constexpr predObj = CALLER_OF(pred);
      

      已经有a proposal 将其内置到语言中,如下所示:

      auto constexpr predObj = (args...) => pred(std::forward<decltype(args)>(args)...);
      

      但被拒绝了。

      CALLER_OF_WITH_CAPTURE(和@Barry 的提议)有一个怪癖,即 noexcept/decltype 返回测试是在在任何捕获的上下文中完成的,而主体是在 在任何捕获的上下文。所以CALLER_OF_WITH_CAPTURE 可能会因此而出现令人惊讶的错误。

      CALLER_OF 中,参数可以是任何一系列标记,除了不平衡的收盘)

      【讨论】:

        【解决方案3】:

        你可以创建一个类似的宏

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

        这将使您可以将任何可调用对象包装到闭包对象中。你会像这样使用它

        auto constexpr predObj = FUNCTORIZE(pred);
        

        【讨论】:

        • 嗨,很抱歉反馈迟了,但我忘记了这个问题。考虑到我没有写不使用宏,你的回答非常好。你会说我问的是not没有宏是可能的吗?
        • @Enlico 不用担心。我认为没有宏就无法做到这一点。您不能将重载函数传递给另一个函数(这就是您提出问题的原因),因此该路由已退出,AFAIK 仅将宏作为自动选项的选项。宏并不邪恶,它们有合法用途,就像这个用例一样。
        • 谢谢。在我写这个问题的时候,我意识到我基本上是在告诉自己为什么这是不可能的,但是让知识渊博的人确认它总是好的!接受。
        • @Enlico 不客气。 C++ 相当复杂,因此获得第二、第三甚至第四意见从来没有什么坏处。 :)
        • noexcept² 在这里的重要性是什么?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-03
        • 2016-12-27
        相关资源
        最近更新 更多