I'm still learning how to use variadic templates. Basically what I want to do is take an STLContainer
that contains elements of type String
. An STL container does not take a fixed number of parameters so I tried using a variadic template. If I understand correctly, I should be able to write this:
/* Take a container of String and return a string of those elements separated by commas.*/
template < template <typename String, typename ... Traits > class STLContainer >
String as_comma_list(const STLContainer<String, Traits> &container)
{
String result;
for (auto it = container.begin(); it != container.end(); it++)
{
result += *it;
result += ",";
}
result.pop_back(); // pop last comma
return result;
}
However the compiler (Apple LLVM version 8.1.0
) spits out:
error: use of undeclared identifier 'Traits'
Any help is much appreciated.
Edit: I ultimately have chosen @Pixelchemist's answer since it seems like the most "generic proof" solution and offered insight into my code. However, I would like to say that @Walter's answer is equivalently good. While @max66's answer was the simplest that fixed the problem, the original problem was that I tried to describe an STL container erroneously.
template < template <class...> class STLContainer, class String, class ...Traits>
String as_comma_list(const STLContainer<String, Traits...> &container)
The type STLContainer
must take a template parameter. If I write a class simplestringvector
(which is not generic I know, but nobody can't stop me :)) your code won't work for me.
The STLContainer
type must provide begin()
and end()
members. (No free functions; no std::begin
and std::end
)
The String
type must provide a pop_back()
member.
The String
type must have operator+=
defined which must be capable of dealing with a string literal containing char
(no wchar_t
, no char16_t
, ...).
And others that are less of an issue here.
No warranty as I'm quite tired but anyway...
If your code requires the type to be iterable anyway you don't need to know the type of the String in the first place. You can get it as the decayed result of dereferencing the container iterator. By dragging std::begin
and std::end
into the scope you can enable ADL for free begin and end functions while still catching the member functions via the std functions.
First some headers and a helper class:
#include <type_traits>
#include <iterator>
namespace detail
{
template<class ...> struct comma;
template<> struct comma<char>
{ static constexpr char c = ','; };
template<> struct comma<wchar_t>
{ static constexpr wchar_t c = L','; };
template<> struct comma<char16_t>
{ static constexpr char16_t c = u','; };
template<> struct comma<char32_t>
{ static constexpr char16_t c = U','; };
}
Now we try to implement as_comma_list
with ADL enabled iteration and without any constraints with respect to the template layout of the container or string.
template <class C>
auto as_comma_list(C&& c)
{
using std::begin;
using std::end;
using string_type = std::decay_t<decltype(*begin(c))>;
using char_type = std::decay_t<decltype(*begin(*begin(c)))>;
string_type result;
auto const ec = end(c);
for (auto it = begin(c); it != ec; )
{
result += *it;
if (++it != ec)
{
result += detail::comma<char_type>::c;
}
else break;
}
return result;
}
Note: This example requires the String type to be iterable as well (which is very common) and it has a second branch in the loop which might be slower when dealing with billions of strings here.