How do I route "c.foo () + c.boo ()" to "c.foo_and_boo ()"?

There is a class C

with methods foo

, boo

, boo_and_foo

and foo_and_boo

. Each method receives clock cycles, respectively i

, j

, k

and l

, where i < j

, k < i+j

, and l < i+j

.

class C
{
public:
  int foo() {...}
  int boo() {...}
  int boo_and_foo() {...}
  int foo_and_boo() {...} };

      

In the code, you can write:

C c;
...
int i = c.foo() + c.boo();

      

But it would be better to have:

int i = c.foo_and_boo();

      

What changes or methods can be made to the definition C

that would allow the syntax to be similar to the original use, but the compiler generated the latter instead. Note that foo

and boo

are not commutative.

Idea 1 Thanks for the comments!

I thought that the creator of the class cHelper

and the class cOps

with a string member ops

, so after cHelper ch;

we can writeint i = ch.foo() + ch.boo();

  • ch.foo()

    returns nothing: cOps("foo")

  • ch.boo()

    also returns: cOps("boo")

  • overloads the "+" operator for the cOps class to cOps("foo") + cOps("boo")

    actually returncOps("foo,boo")

  • overloads the int

    type conversion operator cOps

    , so it int i = cOps("foo") ;

    actually calls c.foo();

    , and int i = cOps("foo,boo") ;

    actually callsc.foo_and_boo();

A bit ugly ....

+3


source to share


1 answer


The goal sounds pretty similar to what Todd Veldhuizen first gave years ago for Blitz ++ : efficiently aggregates operations into an expression tree to effectively reorder computations. In the case of Blitz ++, this was done for matrix operations. This technology is known as Expression Templates .

The general idea is not to concatenate the string and its operations, but rather to code the required operations in the return type. Similar to what is described in the question, expression evaluations are delayed until they are needed. Using specializations for specific operations, the compiler will be chosen to select the appropriate combinations. It might look something like this:

#include <iostream>

namespace C_ops { struct C_tag {}; }

struct C_foo;
struct C_bar;

class C: C_ops::C_tag {
    int value;
public:
    explicit C(int value): value(value) {}
    C_foo foo() const;
    C_bar bar() const;

    int actual_foo() const {
        std::cout << "compute foo(" << value << ")\n";
        return value;
    }
    int actual_bar() const {
        std::cout << "compute bar(" << value << ")\n";
        return value;
    }
    int foo_and_bar(C const& r) const {
        std::cout << "compute foo_and_bar(" << this->value << ", " << r.value << ")\n";
        return this->value;
    }
    int bar_and_foo(C const& r) const {
        std::cout << "compute bar_and_foo(" << this->value << ", " << r.value << ")\n";
        return this->value;
    }
};

struct C_foo: C_ops::C_tag {
    C const& c;
    C_foo(C const& c): c(c) {}
    operator int() const { return c.actual_foo(); }
};
struct C_bar: C_ops::C_tag {
    C const& c;
    C_bar(C const& c): c(c) {}
    operator int() const { return c.actual_bar(); }
};

C_foo C::foo() const { return C_foo(*this); }
C_bar C::bar() const { return C_bar(*this); }

template <typename L, typename R>
struct C_add;
 template <>
 struct C_add<C_foo, C_bar> {
    C_foo l;
    C_bar r;
    C_add(C_foo const& l, C_bar const& r): l(l), r(r) {}
    operator int() const { return l.c.foo_and_bar(r.c); }
};
template <>
struct C_add<C_bar, C_foo> {
    C_bar l;
    C_foo r;
    C_add(C_bar const& l, C_foo const& r): l(l), r(r) {}
    operator int() const { return l.c.bar_and_foo(r.c); }
};
// more specializations, e.g., to deal with C on the LHS

namespace C_ops {
    template <typename L, typename R>
    C_add<L, R> operator+(L const& l, R const& r) {
        return C_add<L, R>(l, r);
    }
}

template <typename... T> void use(T const&...) {}
int main()
{
    C c0(0), c1(1);
    int r0 = c0.foo();
    int r1 = c0.bar();
    int r2 = c0.foo() + c1.bar();
    int r3 = c0.bar() + c1.foo();
    std::cout << "done\n";
    use(r0, r1, r2, r3); // make them used to not have them optimized away (for now)
}

      



The above setup will look at the four expressions you mentioned. Probably more specializations are required, like for everyone c + c.foo()

, c + c

etc., but the general principle doesn't really change. You can protect actual_foo()

and actual_bar()

against access and make the appropriate classes that require a call friend

s.

The need for an additional namespace C_ops

is to provide only "catch" operators that capture operator+()

for all types if they are recovered in that namespace. There is also a tag-type for this, which has no other purpose than to manipulate the ADL (dependent on look-up arguments) to look up that namespace.

This is a pretty nice technique that has worked fairly reliably with C ++ 03: since expressions are evaluated when the target type (in the above case int

) is needed, and C ++ 03 doesn't actually allow capture of an intermediate type, while potentially involved the temporary object was no longer around (the only thing that needed to catch the type of the expression in the inferred context would be when passing it to the function template), there were no problems with lifespan. With C ++ 11, it is necessary to capture the type of objects with a help auto

that will capture the type of the expression rather than evaluate the result, it is probably necessary to think properly about the life of the objects involved. The above example only uses objectsconst

, but you will probably need to be more careful not to allow grabbing of certain temporary objects, most notably type objects C

. It is not clear from your descriptions if they will be expensive objects: the ideal solution would be to capture all objects at cost, but this can be too expensive.

+3


source







All Articles