c++ - 语法糖 : automatically creating simple function objects

标签 c++ templates template-meta-programming

我要实现一组类模板和两个特殊变量,_1_2 .

他们应该使以下内容成为合法代码:

// Sort ascending
std::sort(a, a+5, _1 > _2);

// Output to a stream
std::for_each(a, a+5, std::cout << _1 << " ");

// Assign 100 to each element
std::for_each(a, a+5, _1 = 100);

// Print elements increased by five 5
std::transform(a, a+5, std::ostream_iterator<int>(std::cout, " "), _1 + 5);

我想 _1 * 5 也应该产生一个一元函数,以及 _1/5 等。
  • 不允许提升
  • 不允许使用 lambda

  • 现在我有 非常对模板和模板元编程的经验很少,所以我什至不知道从哪里开始以及我的类模板的结构应该是什么样子。我特别困惑,因为我不知道在我的类模板中是否必须为所有这些编写实现 operator= , operator>> , operator+ , ...- , ...* , .../单独 - 或者有更通用的方法来做到这一点。

    我将特别感谢提供这些运算符实现示例的答案;模板对我来说仍然是一团糟。

    最佳答案

    好!这确实是一个棘手的家庭作业问题!但是,这也是一个很好的工作和学习问题。

    我认为回答这个问题的最好方法是让您从简单的用例开始,然后逐步构建您的解决方案。

    例如,假设您有以下 std::vector<int>跟...共事:

    std::vector<int> vec;
    vec.push_back(4);
    vec.push_back(-8);
    vec.push_back(1);
    vec.push_back(0);
    vec.push_back(7);
    

    您显然希望允许以下用例:
    std::for_each(vec.cbegin(), vec.cend(), _1);
    

    但是如何允许呢?首先你需要定义 _1然后您需要为 _1 类型的函数调用运算符实现“任何事情都可以”重载.

    Boost Lambda 和 Boost Bind 定义占位符对象的方式 _1 , _2 , ... 是让他们有一个虚拟类型。例如,_1对象可能具有类型 placeholder1_t :
    struct placeholder1_t { };
    placeholder1_t _1;
    
    struct placeholder2_t { };
    placeholder2_t _2;
    

    这种“虚拟类型”经常被非正式地称为标签类型。有许多 C++ 库和 STL 依赖于标签类型(例如 std::nothrow_t )。它们用于选择要执行的“正确”函数重载。本质上,创建具有标签类型的虚拟对象并将它们传递到函数中。该函数不以任何方式使用虚拟对象(实际上,大多数情况下甚至没有为其指定参数名称),但是由于存在该额外参数,编译器能够选择正确的重载来调用.

    让我们扩展 placeholder1_t 的定义通过添加函数调用运算符的重载。请记住,我们希望它接受任何内容,因此函数调用运算符的重载本身将根据参数类型进行模板化:
    struct placeholder1_t
    {
        template <typename ArgT>
        ArgT& operator()(ArgT& arg) const {
            return arg;
        }
    
        template <typename ArgT>
        const ArgT& operator()(const ArgT& arg) const {
            return arg;
        }
    };
    

    而已!我们最简单的用例现在将编译并运行:
    std::for_each(vec.cbegin(), vec.cend(), _1);
    

    当然,它基本上相当于无操作。

    现在让我们处理 _1 + 5 .那个表情应该做什么?它应该返回一个一元函数对象,当用一个参数(某种未知类型)调用时,结果是该参数加 5。为了使它更通用,表达式是一元函数对象 +目的。返回的对象本身就是一个一元函数对象。

    需要定义返回对象的类型。它将是一个带有两个模板类型参数的模板:一元函数类型和被添加到一元函数结果中的对象类型:
    template <typename UnaryFnT, typename ObjT>
    struct unary_plus_object_partfn_t;
    

    “partfn”是指表示二进制 + 部分应用的函数类型运算符(operator)。这种类型的实例需要一元函数对象(类型为 UnaryFnT )和另一个对象(类型为 ObjT )的拷贝:
    template <typename UnaryFnT, typename ObjT>
    struct unary_plus_object_partfn_t
    {
        UnaryFnT m_fn;
        ObjT m_obj;
    
        unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj)
            : m_fn(fn), m_obj(obj)
        {
        }
    };
    

    好的。函数调用运算符也需要重载以允许任何参数。我们将使用 C++11 decltype特征来引用表达式的类型,因为我们事先不知道它是什么:
    template <typename UnaryFnT, typename ObjT>
    struct unary_plus_object_partfn_t
    {
        UnaryFnT m_fn;
        ObjT m_obj;
    
        unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj)
            : m_fn(fn), m_obj(obj)
        {
        }
    
        template <typename ArgT>
        auto operator()(ArgT& arg) const -> decltype(m_fn(arg) + m_obj) {
            return m_fn(arg) + m_obj;
        }
    
        template <typename ArgT>
        auto operator()(const ArgT& arg) const -> decltype(m_fn(arg) + m_obj) {
            return m_fn(arg) + m_obj;
        }
    };
    

    它开始变得复杂,但这段代码并没有什么惊喜。它本质上是说函数调用运算符被重载以接受几乎任何参数。然后它会调用 m_fn (一元函数对象)在参数上并添加 m_obj结果。返回类型是 m_fn(arg) + m_obj 的 decltype .

    现在定义了类型,我们可以编写二元运算符 + 的重载接受类型为 placeholder1_t 的对象在左侧:
    template <typename ObjT>
    inline unary_plus_object_partfn_t<placeholder1_t, ObjT> operator+(const placeholder1_t& fn, ObjT obj)
    {
        return unary_plus_object_partfn_t<placeholder1_t, ObjT>(fn, obj);
    }
    

    我们现在可以编译并运行第二个用例:
    std::transform(vec.cbegin(), vec.cend(), std::ostream_iterator<int>(std::cout, " "), _1 + 5);
    std::cout << std::endl;
    

    输出:
    9 -3 6 5 12
    

    This is basically all that you need to do to solve the problem. Think about how you can write custom functional types, instances of which can be returned by overloads of operators.

    EDIT: Improved the overloads of function call operators by employing pass-by-reference.

    EDIT2: In some cases it will be necessary to store a reference to an object rather than a copy of it. For example, to accommodate std::cout << _1, you will need to store a reference to std::cout in the resulting functional object because the std::ios_base copy constructor is private, and it is impossible to copy construct objects of any class derived from std::ios_base including std::ostream.

    To allow for std::cout << _1, you might want to write a ref_insert_unary_partfn_t template. Such a template, like the example of unary_plus_object_partfn_t above, would be templated on an object type and a unary functional type:

    template <typename ObjT, typename UnaryFnT>
    struct ref_insert_unary_partfn_t;
    

    此模板的实例化实例需要存储对类型 ObjT 的对象的引用。以及类型为 UnaryFnT 的一元函数对象的拷贝:
    template <typename ObjT, typename UnaryFnT>
    struct ref_insert_unary_partfn_t
    {
        ObjT& m_ref;
        UnaryFnT m_fn;
    
        ref_insert_unary_partfn_t(ObjT& ref, UnaryFnT fn)
            : m_ref(ref), m_fn(fn)
        {
        }
    };
    

    像以前一样添加函数调用运算符的重载以及插入运算符的重载,<< .

    std::cout << _1的情况下,返回的对象的类型为 ref_insert_unary_partfn_t<std::basic_ostream<char>, placeholder1_t> .

    关于c++ - 语法糖 : automatically creating simple function objects,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8447680/

    相关文章:

    python - yesno 过滤器的复杂比较?

    c++ - 使用元编程递归初始化 std::array

    c++ - 如何正确检查(常量)重载方法

    c++ - 如何使用基元和特征创建尾随返回类型?

    c++ - 如何任意排序元组的类型?

    c++ - 现代 C++ 是否允许双/嵌套可变参数模板扩展?

    c++ - OpenCV 的 findHomography 产生无意义的结果

    c++ - 为什么boost在函数中实现BOOST_CURRENT_FUNCTION

    c++ - 当类型转换 void 指针指向对象时不调用析构函数

    c++ - 错误 C2440 : '=' : cannot convert from 'int *' to 'int **'