Array choice based on template specialization
Small rant about question / description


  And these arrays: [..]


Be as precise as necessary when describing things. Generalize ("containers") when it helps to make things more clear. But don't say wrong things. These are no arrays, these are sets.

Accessing the sets based on their content type.

std::set&lt;std::shared_ptr&lt;Tickable&gt;&gt; tickables;


The fact that this set contains "tickables" is encoded twice in the line above: The type of that std::set contains the type Tickable and the name (informally) tells us the same. While accessing this set as tickablesis a good and very readable option, this does not lend itself to template programming that much, because the compiler doesn't understand "tickables". It's just another identifier, the meaning (that we humans are able to understand) is lost.

Thus I suggest accessing your sets like set_of&lt;Tickable&gt;(). This is still readable to us, but also provides information for the compiler about the content of the set.

To be able to pull this off I wrap the sets in a helper struct:

template &lt;typename Content&gt;
struct SetWith {
  std::set&lt;Content&gt; set;
};


This only holds a single set (with a single content type). I generalize to multiple content types (and multiple sets) via variable template arguments:

template &lt;typename... Contents&gt;
struct SetsContainer {};

template &lt;typename Content, typename... OtherContents&gt;
struct SetsContainer&lt;Content, OtherContents...&gt;
  : public SetsContainer&lt;OtherContents...&gt;,
    public SetWith&lt;Content&gt;
{};


The first is the template definition that will be used when the template specialization (the second definition above) doesn't match, i.e. when there's no (more) content: SetsContainer&lt;&gt;.

The specialization inherits from the previously defined wrapper (and thus gets a member of type std::set&lt;Content&gt;) and also inherits from a variation of its own type with the Content "removed".

Accessing the sets is now almost trivial:

template &lt;typename C, typename Container&gt;
std::set&lt;C&gt; &amp; set_of(Container &amp; sets) {
    return static_cast&lt;SetWith&lt;C&gt; *&gt;(&amp;sets)-&gt;set;
}

// Use:
SetsContainer&lt;int, bool, char&gt; sets;
set_of&lt;bool&gt;(sets).insert(true);


Of course you can also make set_of an (template) member function of a class containing a SetsContainer named sets and drop the parameter.

Your add is then:

template &lt;typename T&gt;
void add(T actor) {
  set_of&lt;typename std::decay&lt;T&gt;::type&gt;().insert(std::forward&lt;T&gt;(actor));
}


Here's an ideone link to play with the above.

Don't throw away useful information

What if you add the very same Tickable twice? Do you want to be informed about this? Because if its added twice when you didn't intent to, this could help you find bugs.

std::set::insert returns useful information, so why throw it away? Better make your add return e.g. the bool returned by insert.

Heavy copies ahead!

The "new" for syntax has its issues, IMO. Do you know that every time you run that (or any of the other) loop ...

for (std::shared_ptr&lt;Tickable&gt; tick : tickables) // ...


... you're creating a copy of every smart pointer in the set. This is not as bad as making a copy of the Tickables, but it still degrades performance: Every time a smart pointer is created it needs to increment the reference counter, and on every destruction it decrements it again.

Better add a reference to avoid those copies:

for (std::shared_ptr&lt;Tickable&gt; &amp; tick : tickables) // ...


This isn't facebook, don't share that much ;)

This is just a guess, but I'd assume that you misuse std::shared_pointer. Think carefully about who owns (for example) a Tickable. Who controls its lifetime?

If it's the GameEngine, then you are far better of using std::set&lt;Tickable&gt;.

The sets are useless

That said, putting std::shared_pointers inside a std::set (with the default compare) means that its operator&lt; is used by the set to prevent duplicates. But, from above link (emphasis mine):


  Note that the comparison operators for shared_ptr simply compare pointer values; the actual objects pointed to are not compared.


Thus you can happily write (assuming a copy constructor) ...

auto foo = std::make_shared&lt;Tickable&gt;();
auto bar = std::make_shared&lt;Tickable&gt;(*foo); // Calls copy constructor
add(foo);
add(bar);


... and have two copies of a Tickable in your set.