0

I have a class like this one:

struct Base
{
    void aa(int n) const {
        std::cout << "aa() " << field*n << std::endl;
    }

    void bb(int n) const {
        std::cout << "bb() " << field*n*2 << std::endl;
    }

    int field = 2;
};

I want to be able to select, at compile time, one of the two implementations, aa() or bb(), via a call to an operator method. Something like:

Base data;
Magic obj(data);
obj.as_AA() * 33; // should call data.aa(33)
obj.as_BB() * 44; // should call data.bb(44)

data must not be duplicated. And the choice of aa() vs bb() must be resolved at compile time.

I have a solution which uses a downcasting whose behavior is in theory undefined (I think). It builds (with g++ and clang++) and runs perfectly, but still ...

struct AA : public Base
{
    void operator*(int n) const {
        return Base::aa(n);
    }
};

struct BB : public Base
{
    void operator*(int n) const {
        return Base::bb(n);
    }
};

struct Chooser
{
    Base obj;

    template<typename WHICH> // will be either AA or BB
    const WHICH& as() const {
        return static_cast<const WHICH&>( obj ); // downcasting
    }
};

In main.cpp:

Chooser ch;
ch.as<AA>() * 5; // prints "aa() 10"
ch.as<BB>() * 7; // prints "bb() 28"

How unreliable is my solution? (because of the downcasting which is technically undefined)

Do you see alternatives?

Thanks


ps: of course I could trivially use

Base data;
data.aa(33);
data.bb(44);

but I really want to access the different implementations via the same name, ie., the operator*


I could also use a templated operator* in Base and have explicit template specializations, however that would force me to use an ugly syntax, which kind of voids the purpose of the operator:

struct Base {
    \\...
    template<int N> void operator*(int n) const;
};

template<> void Base::operator*<1>(int n) const {
    aa(n);
}

Which requires:

Base data;
data.operator*<1>(44); // ugly
L. Bruce
  • 170
  • 7

2 Answers2

5

You could write the Magic class like this:

struct Magic  {
   
    Magic(Base &b) : b(b) {}
    
    Base &b;
     
    struct AA {
        Base &b;
        void operator*(int n) const {
        return b.aa(n);
     }
    };
    
    struct BB {
        Base &b;
        void operator*(int n) const {
        return b.bb(n);
     }
    };
    
    AA as_AA() { return AA{b}; }
    
    BB as_BB() { return BB{b}; }
};

This avoids any inheritance by using composition instead. Also, there is no copy of the data object, since only references are being made to it.

Now you can use exactly the calling syntax that you want, and it has the right behavior:

Base data;
Magic obj(data);
obj.as_AA() * 33; // calls data.aa(33) -- prints 66
obj.as_BB() * 44; // calls data.bb(44) -- prints 176

Here's a demo.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • 1
    This is exactly how I implement my proxy types, too. – Asteroids With Wings Aug 05 '20 at 16:28
  • Thanks for the answer @cigien. I thought about this solution, but I was not sure because it creates temporary objects, and it copies a reference to the Base object. I guess return value optimization would make it a bit better, but... I am looking for a solution with 0 overhead. The line I use, `ch.as() * 44`, really resolves to `aa(44)` with no other operations performed (I think) – L. Bruce Aug 05 '20 at 16:42
  • References are cheap, so that's fine. I guess `AA` and `BB` are technically being copied, but at -O2 the compiler appears to call `aa` and `bb` directly. [demo](https://godbolt.org/z/zzPh1b) – cigien Aug 05 '20 at 16:45
3

One solution for using the same function name is to strongly type the argument:

struct AA {
    int n;
};

struct BB {
    int n;
};

void call(Base& base, AA arg) {
    base.aa(arg.n);
}

void call(Base& base, BB arg) {
    base.bb(arg.n);
}

...

Base data;
call(data, AA{33});
call(data, BB{44});

I took the liberty of getting rid of the operator overloading since this still accesses different implementations using the same name.

If you're trying to go further by having the same calling code with the selection being done in advance, you can use a higher-order function:

auto call_aa(Base& base) {
    return [&](int n) { return base.aa(n); };
}

auto call_bb(Base& base) {
    return [&](int n) { return base.bb(n); };
}

...

Base data;
auto aa = call_aa(data);
aa(33);
call_bb(data)(44);
chris
  • 60,560
  • 13
  • 143
  • 205