Template function for splitting strings in C++
Well, the formatting is peculiar — I can’t see where the function begins.



ContainerT &amp;cont
const CharT *delimiter


The style in C++ is to put the * or &amp; with the type, not the identifier.  This is called out specifically near the beginning of Stroustrup’s first book, and is an intentional difference from C style.



std::size_t start = 0, end;
  ⋮
while ((end = str.find(delimiters, start)) !=
       std::basic_string_view&lt;CharT&gt;::npos)


⧺ES.10: Declare one name (only) per declaration.

Meanwhile, you can declare end just where it is needed, not ahead of time.

while (size_t end=str.find(delimiters, start); end != str.npos)


You can also make the clauses of the condition more readable by making the names short:  For example, see that I used the same instance str to get npos rather than its class name.




  The function can split std::basic_string, not only std::string.
  How string_view in C++17 can help in this example. My version seems not take full advantages of it.


std::basic_string_view&lt;CharT&gt; str,


Your input argument str will be able to take basic_string of any character type, but only using the default Traits.  You might do that on purpose, if Traits are never used other than the defaults?

A string_view will take a string_view, string, or C-style lexical string.


  The delimiter can be a CharT, or const CharT* p or std::basic_string&lt;CharT&gt;


So this first form will take the latter two types, as well as string_view.  You do not need to overload to take CharT delimiter separately.

You do not need to overload to take basic_string for both parameters, because the string_view will take those!

So, two of the overloads should just be removed.  The last one, that takes a CharT delimiter, needs to take the string_view for str so it will handle different types for that — you have it taking a basic_string only, which will be inefficient (copied!) if passed a const CharT* or string_view.



template &lt;typename CharT, typename ContainerT&gt;
void split ( ⋯, ContainerT&amp; conts)


Don’t use “out” parameters.  Return values (⧺F.20).

I can see the appeal here of getting template argument deduction on the return value, but that does not make up for the lack of composibility.  You can add a default type to the ContainerT template parameter, which is handy for the case where the user doesn’t care.

template &lt;typename CharT, typename ContainerT = vector&lt;basic_string&lt;CharT&gt;&gt;
ContainerT split ( ⋯ )


You are assuming that the container holds elements which have the form of constructor you are expecting.

Anyway, now your first example in main becomes:

auto strs = split (str, ",");




template &lt;typename Iter&gt;
void print(Iter begin, Iter end) {
    for (auto it = begin; it != end; ++it) {
        std::cout &lt;&lt; *it &lt;&lt; "/";
    }
    std::cout &lt;&lt; std::endl;
}


    print(strs.begin(), strs.end());


You should just take the container, not separate begin/end.  That not only makes passing easier, but makes the function easier too!

template &lt;typename R&gt;
void print(const R&amp; rng) {
    for (const auto&amp; item : rng) {
        std::cout &lt;&lt; item &lt;&lt; "/";
    }
    std::cout &lt;&lt; '\n';
}


    print(strs);


Oh, and don’t use std::endl.



I also should point out that it’s good practice how you have unit tests for your function!