c++templatesc++17virtual-functionsmultidispatch

How to design an "Awaitable" base class?


The C++ standard library in many places offers a kind of "awaitable" API: e.g. std::future and std::condition_variable can instantly "try" to get their value, "wait" undefinitely for their value, "wait_for" a certain std::chrono::duration, or "wait_until" a certain std::chrono::time_point is reached. I am struggling to create an abstract base class that captures these same operations.

template <typename T>
class awaitable {
 public:
  virtual std::optional<T> try() = 0;

  virtual std::optional<T> wait() = 0;

  template <typename Rep, typename Period>
  virtual std::optional<T> wait_for(const std::chrono::duration<Rep, Period>&) = 0;

  template <typename Clock, typename Duration>
  virtual std::optional<T> wait_until(const std::chrono::time_point<Clock, Duration>&) = 0;
};

try and wait are of no issue. wait_for and wait_until require template parameters and can therefore not be virtual.

Is there a "clean" way to define an interface like this?`

Some options I have considered which (unless I am missing something) do not seem viable:

So far, the third variant seems like it would be my only option at all, and not a great one.


Solution

  • I think your third way might be the way to go. In doubt, you have to apply some effort to limit possible misusage.

    You can also take a look at the acyclic visitor pattern:

    https://en.wikipedia.org/wiki/Visitor_pattern

    Acyclic Visitor C++

    For some scenarios, the implementation way provided by Andrei Alexandrescu (can really recommend his according book) helped me a lot. It requires some efforts to fully understand, is a bit intrusive and as far as I know, it's not possible to stay 100% macro free here at least for C++ < 14, but it has the huge advantage of decentralization you might need here. Also its minor dynamic_cast-usage (not misused as a dynamic switch) is not really an issue for the majority of use-cases and modern architectures.