Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>There are several approaches, each with their own pros and cons. Below three approaches with a cost-benefit analysis.</p> <h2>ADL through custom non-member <code>begin()</code> / <code>end()</code></h2> <p>The first alternative provides non-member <code>begin()</code> and <code>end()</code> function templates inside a <code>legacy</code> namespace to retrofit the required functionality onto any class or class template that can provide it, but has e.g. the wrong naming conventions. Calling code can then rely on ADL to find these new functions. Example code (based on comments by @Xeo):</p> <pre><code>// LegacyContainerBeginEnd.h namespace legacy { // retro-fitting begin() / end() interface on legacy // Container class template with incompatible names template&lt;class C&gt; auto begin(Container&amp; c) -&gt; decltype(c.legacy_begin()) { return c.legacy_begin(); } // similarly for begin() taking const&amp;, cbegin(), end(), cend(), etc. } // namespace legacy // print.h template&lt;class C&gt; void print(C const&amp; c) { // bring into scope to fall back on for types without their own namespace non-member begin()/end() using std::begin; using std::end; // works for Standard Containers, C-style arrays and legacy Containers std::copy(begin(c), end(c), std::ostream_iterator&lt;decltype(*begin(c))&gt;(std::cout, " ")); std::cout &lt;&lt; "\n"; // alternative: also works for Standard Containers, C-style arrays and legacy Containers for (auto elem: c) std::cout &lt;&lt; elem &lt;&lt; " "; std::cout &lt;&lt; "\n"; } </code></pre> <p><strong>Pros</strong>: <strong>consistent and terse calling convention that works completely generically</strong> </p> <ul> <li>works for any Standard Container and user-types that define member <code>.begin()</code> and <code>.end()</code></li> <li>works for C-style arrays</li> <li>can be retrofitted to work (also for <strong>range-for loops</strong>!) for any <strong>class template</strong> <code>legacy::Container&lt;T&gt;</code> that does not have member <code>.begin()</code> and <code>end()</code> without requiring source code modifications</li> </ul> <p><strong>Cons</strong>: requires using-declarations in many places</p> <ul> <li><code>std::begin</code> and <code>std::end</code> are required to have been brought into every explicit calling scope as fall back options for C-style arrays (potential pitfall for template headers and general nuisance)</li> </ul> <h2>ADL through custom non-member <code>adl_begin()</code> and <code>adl_end()</code></h2> <p>A second alternative is to encapsulate the using-declarations of the previous solution into a separate <code>adl</code> namespace by providing non-member function templates <code>adl_begin()</code> and <code>adl_end()</code>, which can then also be found through ADL. Example code (based on comments by @Yakk):</p> <pre><code>// LegacyContainerBeginEnd.h // as before... // ADLBeginEnd.h namespace adl { using std::begin; // &lt;-- here, because otherwise decltype() will not find it template&lt;class C&gt; auto adl_begin(C &amp;&amp; c) -&gt; decltype(begin(std::forward&lt;C&gt;(c))) { // using std::begin; // in C++14 this might work because decltype() is no longer needed return begin(std::forward&lt;C&gt;(c)); // try to find non-member, fall back on std:: } // similary for cbegin(), end(), cend(), etc. } // namespace adl using adl::adl_begin; // will be visible in any compilation unit that includes this header // print.h # include "ADLBeginEnd.h" // brings adl_begin() and adl_end() into scope template&lt;class C&gt; void print(C const&amp; c) { // works for Standard Containers, C-style arrays and legacy Containers std::copy(adl_begin(c), adl_end(c), std::ostream_iterator&lt;decltype(*adl_begin(c))&gt;(std::cout, " ")); std::cout &lt;&lt; "\n"; // alternative: also works for Standard Containers, C-style arrays and legacy Containers // does not need adl_begin() / adl_end(), but continues to work for (auto elem: c) std::cout &lt;&lt; elem &lt;&lt; " "; std::cout &lt;&lt; "\n"; } </code></pre> <p><strong>Pros</strong>: consistent calling convention that works completely generically </p> <ul> <li>the same pros as for @Xeo's suggestion +</li> <li>the repeated using-declarations have been encapsulated (DRY)</li> </ul> <p><strong>Cons</strong>: a little verbose</p> <ul> <li><code>adl_begin()</code> / <code>adl_end()</code> is not as terse as <code>begin()</code> / <code>end()</code></li> <li>it is perhaps also not as idiomatic (although it is explicit)</li> <li>pending C++14 return type deduction, will also pollute namespace with <code>std::begin</code> / <code>std::end</code></li> </ul> <p><strong>NOTE</strong>: Not sure if this really improves upon the previous approach.</p> <h2>Explicitly qualifying <code>std::begin()</code> or <code>std::end()</code> everywhere</h2> <p>Once the verbosity of <code>begin()</code> / <code>end()</code> has been given up anyway, why not go back to the qualified calls of <code>std::begin()</code> / <code>std::end()</code>? Example code:</p> <pre><code>// LegacyIntContainerBeginEnd.h namespace std { // retro-fitting begin() / end() interface on legacy IntContainer class // with incompatible names template&lt;&gt; auto begin(legacy::IntContainer&amp; c) -&gt; decltype(c.legacy_begin()) { return c.legacy_begin(); } // similary for begin() taking const&amp;, cbegin(), end(), cend(), etc. } // namespace std // LegacyContainer.h namespace legacy { template&lt;class T&gt; class Container { public: // YES, DOCUMENT REALLY WELL THAT THE EXISTING CODE IS BEING MODIFIED auto begin() -&gt; decltype(legacy_begin()) { return legacy_begin(); } auto end() -&gt; decltype(legacy_end()) { return legacy_end(); } // rest of existing interface }; } // namespace legacy // print.h template&lt;class C&gt; void print(C const&amp; c) { // works for Standard Containers, C-style arrays as well as // legacy::IntContainer and legacy::Container&lt;T&gt; std::copy(std::begin(c), std::end(c), std::ostream_iterator&lt;decltype(*std::begin(c))&gt;(std::cout, " ")); std::cout &lt;&lt; "\n"; // alternative: also works for Standard Containers, C-style arrays and // legacy::IntContainer and legacy::Container&lt;T&gt; for (auto elem: c) std::cout &lt;&lt; elem &lt;&lt; " "; std::cout &lt;&lt; "\n"; } </code></pre> <p><strong>Pros</strong>: consistent calling convention that works almost generically </p> <ul> <li>works for any Standard Container and user-types that define member <code>.begin()</code> and <code>.end()</code></li> <li>works for C-style arrays</li> </ul> <p><strong>Cons</strong>: a little verbose and retrofitting is not generic and a maintainence problem</p> <ul> <li><code>std::begin()</code> / <code>std::end()</code> is a little more verbose than <code>begin()</code> / <code>end()</code></li> <li>can only be retrofitted to work (also for <strong>range-for loops</strong>!) for any <strong>class</strong> <code>LegacyContainer</code> that does not have member <code>.begin()</code> and <code>end()</code> (and for which there is no source code!) by providing explicit specializations of the non-member function templates <code>begin()</code> and <code>end()</code> in <code>namespace std</code></li> <li>can only be retrofitted onto <strong>class templates</strong> <code>LegacyContainer&lt;T&gt;</code> by directly adding member functions <code>begin()</code> / <code>end()</code> inside the source code of <code>LegacyContainer&lt;T&gt;</code> (which for templates is available). The <code>namespace std</code> trick does not work here because function templates cannot be partially specialized. </li> </ul> <h2>What to use?</h2> <p>The ADL approach through non-member <code>begin()</code> / <code>end()</code> in a a container's own namespace is the idiomatic C++11 approach, especially for generic functions that require retrofitting on legacy classes and class templates. It is the same idiom as for user-providing non-member <code>swap()</code> functions.</p> <p>For code that only uses Standard Containers or C-style arrays, <code>std::begin()</code> and <code>std::end()</code> could be called everywhere without introducing using-declarations, at the expense of more verbose calls. This approach can even be retrofitted but it requires fiddling with <code>namespace std</code> (for class types) or in-place source modifcations (for class templates). It can be done, but is not worth the maintainence trouble.</p> <p>In non-generic code, where the container in question is known at coding-time, one could even rely on ADL for Standard Containers only, and explicitly qualify <code>std::begin</code> / <code>std::end</code> for C-style arrays. It loses some calling consistency but saves on using-declarations.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

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