paperjam
paperjam

Reputation: 8528

C++ cascaded operator[] to operator() parameter list?

I have a class with operator() like this:

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

Example usage:

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

I need my users to be able to use an alternate syntax:

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

I know I need to add S::operator[](int a) and that it needs to return a helper object. But beyond that it all gets a bit complex and I have a feeling that I am reinventing the wheel since other libraries (e.g. multidimensional arrays) probably already offer similar interface.

Ideally I'd just use an existing library to achieve this goal. Failing that, how can I achieve my goal with the most generic code?

Edit: ideally I'd like to achieve this without any runtime penalty on a modern optimizing compiler.

Upvotes: 2

Views: 251

Answers (4)

I would avoid this altogether and offer just operator(), but if you really want to give it a shot, the idea is that your type's operator[] would return an object of a helper type that holds both a reference to your object and the value that was passed in. That helper class will implement operator[] by again storing a reference to the original object and the arguments to both calls to []. This would have to be done for all but the last level (I.e. a fair amount of helpers). I the last level, operator[] will take its argument together with all previously stored values and call operator() with all of the previously stored values plus the current value.

A common way of phrasing this is saying that each intermetiate type binds one of the arguments of the call to operator(), with the last one executing the call with all bound arguments.

Depending on whether you want to support more or less number of dimensions of arrays you might want/need to complicate this even more to make it generic. In general it is not worth the effort and just offering operator() is usually the solution. Remember that it is better to keep things as simple as possible: less effort to write and much less effort to maintain.

Upvotes: 1

Steve Jessop
Steve Jessop

Reputation: 279265

This is an attempt at the bind approach. I doubt that it's particularly efficient, and it has some nasty bits in it, but I post it in case anyone knows how to fix it. Please edit:

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));
}

where s is an expression that returns operator() bound to this. Something along the lines of std::bind(std::mem_fun(S::operator(), this, _1, _2, _3, _4)). Although I can't remember whether std::bind can already handle member functions, mem_fun might not be needed.

function_type<N>::type is std::function<int, [int, ... n times]>, and bound<N> is function_type<N>::type bound(function_type<N+1>::type f, int p) { return std::bind(f, p, _1, _2, ... _N); }. I'm not immediately sure how to define those recursively, but you could just list them up to some limit.

Upvotes: 1

paperjam
paperjam

Reputation: 8528

Here is a Fusion implementation that supports arbitrary parameter and return types. Kudos to anyone that can get this working (please let me know if you do)!

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);
};

Upvotes: 0

Kos
Kos

Reputation: 72271

Here we go!

First of all, the code is kind of messy- I have to accumulate the argument values as we go, and the only way I could think of (at least in C++03) is to pass the immediate indices set around as arrays.

I have checked this on G++ 4.5.1 (Windows / MinGW) and I confirm that on -O3 the call:

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

yields the same assembler code as:

s(1,2,3,4);

So - no runtime overhead if your compiler is smart with optimisations. Good job, GCC team!

Here goes the code:

#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);
    }

};

And seriously, please, don't use this and just stick with operator(). :) Cheers!

Upvotes: 4

Related Questions