Undeclared identifier for Variadic Template

394 views Asked by At

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.

3

There are 3 answers

5
Pixelchemist On BEST ANSWER

Your code would have to look like this:

template < template <class...> class STLContainer, class String, class ...Traits>
String as_comma_list(const STLContainer<String, Traits...> &container)

but let's look at the implications of your code:

  1. 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.

  2. The STLContainer type must provide begin() and end() members. (No free functions; no std::begin and std::end)

  3. The String type must provide a pop_back() member.

  4. 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, ...).

  5. And others that are less of an issue here.

The function can be more generic:

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.

4
max66 On

What about

template <template <typename...> class STLContainer,
          typename String, typename Traits> 
String as_comma_list(const STLContainer<String, Traits> &container)

?

But you need Traits?

I suppose that you can simplify your code as follows

template <template <typename...> class STLContainer, typename String> 
String as_comma_list(const STLContainer<String> &container)
1
Walter On

Trying to write generic code in this way is bound to fail, because in general containers cannot be described as container<T, traits...>, think about map<key, T, Compare, Allocator>, which has value_type=pair<key, T>.

Instead, in C++, this type of generic programming is usually done via iterators (as all over the standard library), e.g.

template<typename It>
enable_if_t<is_same<string, typename iterator_traits<It>::value_type>::value,
            string>  // or use static_assert() in the function body
as_comma_list(It begin, const It &end)
{
    string result;
    for(; begin!=end; ++begin)
    {
        result += *begin;
        result += ",";
    }
    result.pop_back();
    return result;
}