0

I want to ensure that instances of Thing created by different instances of Thing::Factory cannot be combined.

The following code works does just that, at runtime:

#include <cassert>

struct Thing {
  struct Factory {
    Thing make() {return Thing(this);}
  };

  static void combine(Thing t1, Thing t2) {
    assert(t1.factory == t2.factory);
  }

private:
  Thing(Factory* factory_) : factory(factory_) {}
  Factory* factory;
};

int main() {
  Thing::Factory f1;
  Thing t11 = f1.make();
  Thing t12 = f1.make();
  Thing::combine(t11, t12);

  Thing::Factory f2;
  Thing t21 = f2.make();
  Thing t22 = f2.make();
  Thing::combine(t21, t22);

  Thing::combine(t11, t21); // Assertion failure
}

Question: is there a way to do that during compilation?

I've tried making Thing a template:

template<typename Tag>
struct Thing {
  // Same code as before
};

And modifying client code to:

struct Tag1;
struct Tag2;

int main() {
  Thing<Tag1>::Factory f1;
  Thing<Tag1> t11 = f1.make();
  Thing<Tag1> t12 = f1.make();
  Thing<Tag1>::combine(t11, t12);

  Thing<Tag2>::Factory f2;
  Thing<Tag2> t21 = f2.make();
  Thing<Tag2> t22 = f2.make();
  Thing<Tag2>::combine(t21, t22);
}

Then there is no way to combine t11 and t21. OK.

But there are still issues:

  • nothing forbids to create another Thing<Tag1>::Factory and to combine the Things it makes with t11
  • client has to manually declare tag types

Is there a pattern that could solve those as well?

jacquev6
  • 620
  • 4
  • 18

2 Answers2

0
  • nothing forbids to create another Thing::Factory and to combine the Things it makes with t11

You can have a common factory base, which keeps a static std::set of tag std::type_info instances initialized in the factory constructors.

If the entry is already in that std::set, you can throw a runtime exception

  • client has to manually declare tag types

You can also generate IDs for the factory instances instead of having a template parameter.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
0

Before answering, it sounds like you're having an X-Y problem; and - I doubt you've chosen the right Y. Consider dropping these factories altogether, or alternatively - using just one factory for many classes with a common base class.


For this to be possible at compile-time, either the factories need to produce values of different types, or combine() has to be able to run at compile time. But making combine() constexpr wouldn't help you, since the factories only produce output at run time... so yes, type differentiation is your only option. Some sort of tagging is fine, I suppose - but if you can provide these tags apriori, why not just have different types from the get-go? Like you said yourself, it's not clear where these tags are supposed to come from.

Anyway, to forbid creating two factories of the same type, you can use a singleton pattern. The factory works at runtime anyway, might as well let it be created at runtime.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • I indeed had an X-Y problem and ended-up choosing a totally different design. Thanks for the insight. – jacquev6 Aug 31 '18 at 07:39
  • I accepted this answer because its second paragraph states conditions required to be able to check during compilation. – jacquev6 Aug 31 '18 at 07:41