Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Despite your misgivings about templates, this really is an appropriate spot to use C++ templates. Template functions perfectly capture the notion of "this function works with any arguments, as long as the operations I perform on those arguments are well-defined."</p> <p>You don't need to worry about inlining in this case. Unless you <em>define</em> <code>f</code> inside of the body of a class, it won't automatically be inlined, even if it's a template. For example, in this code:</p> <pre><code>class MyClass { public: template &lt;typename T&gt; void f(T&amp;); }; template &lt;typename T&gt; void MyClass::f(T&amp;) { /* ... implementation ... */ } </code></pre> <p>Because <code>f</code> isn't <em>defined</em> inside of the <code>MyClass</code> body, it's not considered an inline function.</p> <p>As for your concern about making the header file too large, I contend that this really isn't something to worry about. If you're worried about making the header too large, you can either put a big comment about halfway down saying something like</p> <pre><code> /* * * * * Implementation Below This Point * * * * */ </code></pre> <p>Alternatively, you could make a separate <code>.h</code> file for the template implementation, then <code>#include</code> that file at the bottom of the header file. This shields the client from seeing the template implementations unless they actively go looking for it.</p> <p>Hope this helps!</p> <p><strong>EDIT:</strong> If <code>f</code> is virtual, then you cannot make it a template function (as you've probably figured out). Consequently, if you want to make it work for "things that happen to look like <code>std::list</code>," then you don't have many good options. Normally you'd create a base class for both <code>std::list</code> and your custom list type, but this isn't an option as you can't modify <code>std::list</code>.</p> <p>Fortunately, there is a way to treat <code>std::list</code> and things that look like it polymorphically using a trick called <em>external polymorphism</em>. The idea is that while you can't make the appropriate classes behave polymorphically, you can add an extra layer of indirection around those objects by introducing a polymorphic class hierarchy that just forwards all its requests to the objects that themselves are not polymorphic.</p> <p>If you're willing to pull out the Big Template Guns, you can encapsulate this logic inside of a class that works much the same way as the new <code>std::function</code> template type. The idea is as follows. First, we'll create a polymorphic base class that exports all the functions you want to call as pure virtual functions:</p> <pre><code>class List { public: virtual ~List() {} virtual std::list&lt;int&gt;::const_iterator begin() const = 0; virtual std::list&lt;int&gt;::const_iterator end() const = 0; virtual void push_back(int value) = 0; /* ... etc. ... */ }; </code></pre> <p>Now, we can define a <em>template</em> subclass of <code>List</code> that implements all of the public interface by forwarding all of the calls to an object of the actual type. For example:</p> <pre><code>template &lt;typename T&gt; class ListImpl: public List { private: T&amp; mImpl; // Actual object that does the work public: /* Constructor stores a reference to the object that actually does the work. */ ListImpl(T&amp; impl) : mImpl(impl) { // Handled in initializer list } /* These functions all forward the requests to the implementation object. */ virtual std::list&lt;int&gt;::const_iterator begin() const { return mImpl.begin(); } virtual std::list&lt;int&gt;::const_iterator end() const { return mImpl.end(); } virtual void push_back(int value) { mImpl.push_back(value); } /* ... etc. ... */ }; </code></pre> <p>Now that you have this wrapper, you can implement <code>f</code> so that it takes in a <code>List</code>:</p> <pre><code>class MyClass { public: void f(List* myList) { myList-&gt;push_back(137); // For example } }; </code></pre> <p>And you can call this function on an object that looks like a list by first wrapping it in an object of type <code>ListImpl</code>. For exmaple:</p> <pre><code>MyClass mc; std::list&lt;int&gt; myList; MyIntList myIntList; mc-&gt;f(new ListImpl&lt;std::list&lt;int&gt; &gt;(myList)); mc-&gt;f(new ListImpl&lt;MyIntList&gt;(myIntList)); </code></pre> <p>Of course, this is bulky and unwieldy. You also have to worry about resource leaks, which aren't very fun. Fortunately, you can solve this by wrapping up all the logic to deal with <code>List</code> and <code>ListImpl</code> in a helper class, like this one here:</p> <pre><code>class ListWrapper { public: template &lt;typename ListType&gt; ListWrapper(ListType&amp; list) { /* Store a wrapper of the appropriate type. */ mImpl = new ListImpl&lt;ListType&gt;(list); } /* Delete the associated implementation object. */ ~ListWrapper() { delete mImpl; } /* For each interface function, provide our own wrapper to forward the logic * to the real implementation object. */ std::list&lt;int&gt;::const_iterator begin() const { return mImpl-&gt;begin(); } std::list&lt;int&gt;::const_iterator end() const { return mImpl-&gt;end(); } void push_back(int value) { mImpl-&gt;push_back(value); } /* ... etc. ... */ /* Copy functions necessary to avoid serious memory issues. */ ListWrapper(const ListWrapper&amp; rhs) { mImpl = rhs.mImpl-&gt;clone(); } ListWrapper&amp; operator= (const ListWrapper&amp; rhs) { if (this != &amp;rhs) { delete mImpl; mImpl = rhs.mImpl-&gt;clone(); } return *this; } private: List* mImpl; // Pointer to polymorphic wrapper }; </code></pre> <p>You can now write <code>f</code> to take in a <code>ListWrapper</code> like this:</p> <pre><code>class MyClass { public: virtual void f(ListWrapper list) { list.push_back(137); // For example } }; </code></pre> <p>(This assumes that you've updated <code>List</code> and <code>ListImpl</code> with a virtual <code>clone</code> function that makes a copy of the object, which I've omitted for brevity's sake).</p> <p>And magically, this code is now legal (and safe!):</p> <pre><code>MyClass mc; std::list&lt;int&gt; myList; MyIntList myIntList; mc.f(myList); mc.f(myIntList); </code></pre> <p>This code works because the template constructor for <code>ListWrapper</code> will automatically infer the type of its argument and implicitly create an object of type <code>ListImpl</code> appropriate for that object. It also encapsulates the memory management for you, so you never see any explicit <code>new</code>s or <code>delete</code>s. Moreover, it means that you can pass in any object that you'd like and everything will work automatically - we've essentially made anything that looks like a <code>list</code> polymorphic by using a parallel class hierarchy!</p> <p>Whew! That was fun! Hope this helps!</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload