Home > other >  C template return type for an abstract method (similar to java)
C template return type for an abstract method (similar to java)

Time:10-28

Let say I have a visitor in Java.

interface Visitor<T> {
    T visitA(VisitableA a);
    T visitB(VisitableB b);
}

abstract class Visitable {
    abstract <T> T accept(Visitor<T> visitor);
}

class VisitableA extends Visitable {
    @Override <T> T accept(Visitor<T> visitor) {
        return visitor.visitA(this);
    }
}

class VisitableB extends Visitable {
    @Override <T> T accept(Visitor<T> visitor) {
        return visitor.visitB(this);
    }
}

Now, if I wanted to take that and write it in C , I could do it like this

class VisitableA;
class VisitableB;

template <typename T> class Visitor {
    virtual T visitA(VisitableA& a) = 0;
    virtual T visitB(VisitableB& b) = 0;
};

class MyVisitor : public Visitor<int> {
    int visitA(VisitableA& a) override { /* do something */ return 42; }
    int visitB(VisitableB& b) override { /* do something */ return 1337; }
};

class Visitable {
    template <typename T> virtual T accept(Visitor<T>& visitor) = 0;
};

class VisitableA : public Visitable { 
    template <typename T> T accept(Visitor<T>& visitor) override { return visitor.visitA(*this); }
};

class VisitableB : public Visitable { 
    template <typename T> T accept(Visitor<T>& visitor) override { return visitor.visitB(*this); }
};

But this does not compile, since I cannot have a virtual templated method.

I could use std::any (or something similar) but I was wondering if there was a way do implement this without it.

It must be noted that the accept return type is not known at the construction of the Visitables.

C 20 is fine if required.

CodePudding user response:

Do what Java does under the hood.

Replace T with std::any.

For Visitable make a base class that returns any to both methods. Then make a template class that implements the any returning as final, and creates a T returning Visit as pure for implementors to implement.

Visitable has two methods; a pure virtual private one taking a Visitable, and a public template taking a VitiableT template. It calls the pure virtual one and casts the any to a T.

Java generics are basically just writing that glue for you.

You may have to do casting under the hood, or look at typeids, depending on how you use this.

struct VistableA; struct VistableB;
struct Visitor{
  virtual std::any visitA(VisitableA&)=0;
  virtual std::any visitB(VisitableB&)=0;
};
template<class T>
struct VisitorT:Visitor{
  std::any visitA(VisitableA& a) final{ return visitTA(a); }
  T visitTA(VisitableA& a) = 0;
  std::any visitB(VisitableB& b) final{ return visitTB(b); }
  T visitTB(VisitableB& b) = 0;
};

class MyVisitor : public VisitorT<int> {
  int visitTA(VisitableA& a) override { /* do something */ return 42; }
  int visitTB(VisitableB& b) override { /* do something */ return 1337; }
};

class Visitable {
  template <typename T>  T accept(VisitorT<T>& visitor) {
    return std::any_cast<T>(accept(static_cast<Visitor&>(visitor)));
  }
  virtual std::any accept(Visitor& visitor)=0;
};

class VisitableA : public Visitable { 
  std::any accept(Visitor& visitor) override { return visitor.visitA(*this); }
};

class VisitableB : public Visitable { 
  std::any accept(Visitor& visitor) override { return visitor.visitB(*this); }
};

now this being C there are another half dozen ways to solve your problem that do not emulate Java. But this is the Java esque way.

Note because we are writing the glue code Java writes for you, if we make a mistake in the glue we aren't type safe.

And in VisitableA Java checks that the same "generic type" is used for all of the Ts; in C I used a std::any, which could be mixed up with other "generics" and break some type safety. You could imagine a complex system of tagged std::any to avoid this.

  • Related