0

Questions about workarounds for templated functions that must be virtual are quite common here, though I couldn't find anything that helps with my problem, which is a simple variation of this question: need a virtual template member workaround

The proposed approach is using type erasure leading to a very clean and simple solution. However, what if I need to return a value from the visit() method? The OP already had this aspect in his question, but since he never used the result it was ignored in the solution.

Now imagine this code instead:

template <typename T>
class BaseVisitor {
  public:
    BaseVisitor();
    T visit(BaseVisited *visited);
    virtual ~BaseVisitor();
}


class BaseVisited {
  BaseVisited();
  template <typename T>
    virtual T accept(BaseVisitor<T> *visitor) { return visitor->visit(this); };
  virtual ~BaseVisited();
}

We still need accept() to be templated, even after applying the type erasure trick. Any further ideas?

Note: I cannot use a base class for the return value, as has been suggested in some of the answers on SO, because T can stand for any base type as well (int, string etc.).

Community
  • 1
  • 1
Mike Lischke
  • 48,925
  • 16
  • 119
  • 181
  • If you're not okay with closed typesystem (e.g. `std::variant<>`) and type erasure (e.g., `boost::any`, `void*`), probably your only choice is to `throw T(...);` instead of `return T;` – lorro Jul 29 '16 at 13:52

1 Answers1

1

You basically have two choices:

  1. Provide a common return type for accept(), like std::any or its predecessor boost::any. Or, if you have some small finite number of possible return types, std::variant/boost::variant. This way, the member function doesn't need to be a template - but the caller has to know what to do with it.

  2. Store the result, typed, in the Visitor object. You have to store it instead of returning it, but at least you can keep the type. You can handle this using the Visitor Pattern. We can have different visitors that have different result types; they just store them internally:

    // a void visitor
    struct ShapePrinter : IShapeVisitor
    {
        void visit(const Square&) override { std::cout << "Square"; }
        void visit(const Circle&) override { std::cout << "Circle"; }
    };
    
    // a double visitor
    struct ShapePerimeterComputer : IShapeVisitor
    {
        void visit(const Square& square) override { perimeter = 4. * square.sideLength; }
        void visit(const Circle& circle) override { perimeter = 2. * M_PI * circle.radius; }
    
        double perimeter = 0.;
    };
    
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Barry
  • 286,269
  • 29
  • 621
  • 977
  • No. 2. comes with wonderful side effects in multi-threaded environment unless you're locking till read. – lorro Jul 29 '16 at 15:00
  • I cannot use C++17 nor Boost, but in fact I have an own Any class that should help here. I'll try that and see if that works out nicely. – Mike Lischke Jul 29 '16 at 15:03