【问题标题】:C++ cascaded operator[] to operator() parameter list?C ++级联运算符[]到运算符()参数列表?
【发布时间】:2012-01-25 12:13:52
【问题描述】:

我有一个operator() 像这样的课程:

struct S
{
    int operator()(int a, int b, int c, int d);
};

示例用法:

S s;
int i = s(1, 2, 3, 4);

我需要我的用户能够使用替代语法:

int i = s[1][2][3][4]; // equivalent to calling s(1, 2, 3, 4)

我知道我需要添加 S::operator[](int a) 并且它需要返回一个辅助对象。但除此之外,这一切都变得有点复杂,我觉得我正在重新发明轮子,因为其他库(例如多维数组)可能已经提供了类似的接口。

理想情况下,我会使用现有的库来实现这一目标。如果做不到这一点,我怎样才能用最通用的代码实现我的目标?

编辑:理想情况下,我希望在现代优化编译器上没有任何运行时损失。

【问题讨论】:

  • 你的助手对象的operator[]需要返回另一个助手对象等
  • “最通用的代码”哈,元编程挑战 :) 让我试试
  • 真的,只要保留operator()...这些辅助结构会变得非常讨厌...
  • 那些其他多维数组库不会从它们的 multi-[] 表达式中返回 void。即使他们的帮助对象最终确实调用了一个 n 参数函数,他们也会期望它返回一个引用。所以我不会打赌能够重复使用很多。我同情所有参与其中的人,尤其是那些认为s[1][2][3][4] 看起来像是一个合理的空洞表达的人!
  • 很好 @Steve 将编辑问题

标签: c++ operator-overloading metaprogramming syntactic-sugar


【解决方案1】:

我们开始吧!

首先,代码有点乱——我必须在进行过程中累积参数值,而我能想到的唯一方法(至少在 C++03 中)是传递周围的立即索引作为数组。

我已经在 G++ 4.5.1 (Windows / MinGW) 上检查了这一点,并确认在 -O3 上调用:

s[1][2][3][4];

产生与以下相同的汇编代码:

s(1,2,3,4);

所以 - 如果您的编译器在优化方面很聪明,则不会产生运行时开销。 干得好,GCC 团队!

代码如下:

#include <iostream>

template<typename T, unsigned N, unsigned Count>
struct PartialResult
{
    static const int IndicesRemembered = Count-1-N;
    T& t;
    int args[IndicesRemembered];
    PartialResult(T& t, int arg, const int* rest) : t(t) {
        for (int i=0; i<IndicesRemembered-1; ++i) {
            args[i] = rest[i];
        }
        if (IndicesRemembered>0) args[IndicesRemembered-1] = arg;
    }
    PartialResult<T, N-1, Count> operator[](int k) {
        return PartialResult<T, N-1, Count>(t, k, args);
    }
};

template<typename T, unsigned Count>
struct PartialResult<T, 0, Count>
{
    static const int IndicesRemembered = Count-1;
    T& t;
    int args[IndicesRemembered];
    PartialResult(T& t, int arg, const int* rest) : t(t) {
        for (int i=0; i<IndicesRemembered-1; ++i) {
            args[i] = rest[i];
        }
        if (IndicesRemembered>0) args[IndicesRemembered-1] = arg;
    }
    void operator[](int k) {
        int args2[Count];
        for (int i=0; i<Count-1; ++i) {
            args2[i] = args[i];
        }
        args2[Count-1] = k;
        t(args2);
    }
};

template<typename T, unsigned Count>
struct InitialPartialResult : public PartialResult<T, Count-2, Count> {
    InitialPartialResult(T& t, int arg)
        : PartialResult<T, Count-2, Count>(t, arg, 0) {}
};

struct C {

    void operator()(const int (&args)[4]) {
        return operator()(args[0], args[1], args[2], args[3]);
    }
    void operator()(int a, int b, int c, int d) {
       std::cout << a << " " << b << " " << c << " " << d << std::endl;
    }
    InitialPartialResult<C, 4> operator[](int m) {
        return InitialPartialResult<C, 4>(*this, m);
    }

};

说真的,请不要使用它,而坚持使用operator()。 :) 干杯!

【讨论】:

  • 除了看起来很吓人之外,这段代码很简单(只有一个模板+一个部分规范),但是如果有什么不清楚的地方,请继续询问。
  • 提问者已将operator() 更改为返回int 而不是void,因此您可以为此更新PartialResult&lt;T, 0, Count&gt;::operator[]。我不认为PartialResult 有足够的模板参数,所以最终的返回类型可以是另一种:-)
  • 很好的答案,谢谢@Kos。我现在想知道将boost::tuple 用于参数类型(如果不同)和值是否有帮助。我似乎记得在某处读过,有一种方法可以从元组中调用带有“爆炸”参数的函数......?编辑:boost.org/doc/libs/1_43_0/libs/fusion/doc/html/fusion/…
【解决方案2】:

这是bind 方法的尝试。我怀疑它是否特别有效,并且其中包含一些令人讨厌的部分,但我将其发布以防万一有人知道如何修复它。请编辑:

template <int N>
struct Helper {
    function_type<N>::type f;
    explicit Helper(function_type<N>::type f) : f(f) {}
    Helper<N-1> operator[](int p) {
        return Helper<N-1>(bound<N-1>(f,p));
    }
};

template<>
struct Helper<0> {
    function_type<0>::type f;
    explicit Helper(function_type<0>::type f) : f(f) {}
    operator int() {
        return f();
    }
};

Helper<3> S::operator[](int p) {
    return Helper<3>(std::bind(s, _1, _2, _3));
}

其中s 是一个表达式,它返回绑定到thisoperator()。类似于std::bind(std::mem_fun(S::operator(), this, _1, _2, _3, _4)) 的东西。虽然我不记得std::bind 是否已经可以处理成员函数,但可能不需要mem_fun

function_type&lt;N&gt;::typestd::function&lt;int, [int, ... n times]&gt;bound&lt;N&gt;function_type&lt;N&gt;::type bound(function_type&lt;N+1&gt;::type f, int p) { return std::bind(f, p, _1, _2, ... _N); }。我不确定如何递归地定义它们,但您可以将它们列出到某个限制。

【讨论】:

  • 如果std::bindstd::function 不是黑盒子,你可以访问参数类型和值会更容易。
【解决方案3】:

我会完全避免这种情况,只提供operator(),但如果你真的想试一试,这个想法是你的类型的operator[] 将返回一个辅助类型的对象,该对象同时包含对你的引用对象和传入的值。该帮助类将通过再次存储对原始对象的引用以及对[]的两个调用的参数来实现operator[]。除了最后一个级别(即相当数量的助手)之外,必须对所有级别都执行此操作。在最后一级,operator[] 将把它的参数连同所有先前存储的值一起调用,并使用所有先前存储的值加上当前值调用 operator()

一种常见的表述方式是,每个中间类型绑定operator() 的调用的一个参数,最后一个使用所有绑定的参数执行调用。

根据您是否希望支持更多或更少维数的数组,您可能希望/需要将其复杂化以使其具有通用性。一般来说,不值得付出努力,只提供operator() 通常是解决方案。请记住,最好让事情尽可能简单:更少的编写工作和更少的维护工作。

【讨论】:

  • 确实 operator() 是我现在正在使用的。调用代码几乎是一种特定于域的语言,[][][][] 语法在这种情况下更容易阅读。
  • “需要提前安抚” - 在手机上写字?
  • 我以前没有听说过它,但是绑定参数很好地描述了帮助类的作用。这让我想知道:是否有可能以某种方式使用boost::bind 使实现变得微不足道? (我目前没有时间进行实验。但这个想法听起来很合理。)
  • @James:我开始按照这些思路写一个答案,但是有些繁琐的部分我没有时间弄清楚如何做得很好。无论如何,我会发布它,作为一个开始,如果有人可以修复它。
  • @SteveJessop:有点……iPad。我刚从西班牙搬到纽约,还没有电脑。我非常讨厌 iPad 中的自动更正功能。关于bind,我什至考虑过尝试在 iPad 上通过 SSH 编译/运行测试程序,但我并没有那么疯狂。
【解决方案4】:

这是一个支持任意参数和返回类型的 Fusion 实现。感谢任何可以让这个工作的人(如果你做到了,请告诉我)!

template <class Derived, class ReturnValue, class Sequence>
struct Bracketeer
{
    typedef ReturnValue result_type;
    typedef boost::fusion::result_of::size<Sequence> Size;

    struct RvBase
    {
        Sequence sequence;
        Derived *derived;
    };

    template <int n>
    struct Rv : RvBase
    {
        Rv(Derived *d) { this->derived = d; }
        Rv(RvBase *p) : RvBase(*p) { }
        Rv<n-1> operator[](typename boost::fusion::result_of::at_c<Sequence const, n-1>::type v)
        {
            boost::fusion::at_c<Size::value - 1 - n>(sequence) = v;
            return Rv<n-1>(this);
        }
    };

    template <>
    struct Rv<0> : RvBase
    {
        Rv(Derived *d) { this->derived = d; }
        Rv(RvBase *p) : RvBase(*p) { }
        ReturnValue operator[](typename boost::fusion::result_of::at_c<Sequence, Size::value - 1>::type v)
        {
            boost::fusion::at_c<Size::value - 1>(sequence) = v;
            return invoke(*derived, sequence);
        }
    };

    Rv<Size::value - 1> operator[](typename boost::fusion::result_of::at_c<Sequence, 0>::type v)
    {
        Rv<Size::value> rv(static_cast<Derived*>(this));
        return rv[v];
    }
};

struct S
    :
    Bracketeer<S, int, boost::fusion::vector<int, int, int, int> >
{
    int operator()(int a, int b, int c, int d);
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-29
    • 2011-05-07
    • 2015-03-09
    • 2019-03-30
    • 1970-01-01
    • 1970-01-01
    • 2013-10-02
    相关资源
    最近更新 更多