Необъявленный идентификатор для шаблона Variadic

Я все еще учусь использовать вариативные шаблоны. По сути, я хочу взять STLContainer, который содержит элементы типа String. Контейнер STL не принимает фиксированное количество параметров, поэтому я попытался использовать вариативный шаблон. Если я правильно понял, то должен написать так:

/* 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;
}

Однако компилятор (Apple LLVM version 8.1.0) выдает:

error: use of undeclared identifier 'Traits'

Буду признателен за любую оказанную помощь.

Редактировать: в конечном итоге я выбрал ответ @Pixelchemist, поскольку он кажется наиболее «универсальным» решением и дает представление о моем коде. Однако я хотел бы сказать, что ответ @Walter одинаково хорош. Хотя ответ @max66 был самым простым, который устранил проблему, исходная проблема заключалась в том, что я пытался ошибочно описать контейнер STL.


person Carlos Brito    schedule 15.09.2017    source источник
comment
Traits не является типом; это пакет параметров. Вам необходимо расширить пакет параметров: STLContainer<String, Traits...>   -  person Justin    schedule 15.09.2017
comment
На самом деле у вас также нет пакета параметров; вам нужно, чтобы Traits был не в списке аргументов шаблона STLContainer, а as_comma_list   -  person Justin    schedule 15.09.2017
comment
Контейнеры STL в общем случае не могут быть описаны как container<type,traits...>. Рассмотрим std::map<key,T,compare,alloc>, у которого есть value_type=std::pair<key,T> ...   -  person Walter    schedule 16.09.2017


Ответы (3)


Ваш код должен выглядеть так:

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

но давайте посмотрим на последствия вашего кода:

  1. Тип STLContainer должен принимать параметр шаблона. Если я напишу класс simplestringvector (который, я знаю, не является универсальным, но никто не может меня остановить :)), ваш код не будет работать для меня.

  2. Тип STLContainer должен содержать элементы begin() и end(). (Нет бесплатных функций; нет std::begin и std::end)

  3. Тип String должен предоставлять элемент pop_back().

  4. Тип String должен иметь определенный operator+=, который должен иметь возможность работать со строковым литералом, содержащим char (без wchar_t, без char16_t, ...).

  5. И другие, которые менее важны здесь.

Функция может быть более общей:

Нет гарантии, так как я очень устал, но все же...

Если ваш код требует, чтобы тип был итерируемым, в любом случае вам не нужно знать тип String в первую очередь. Вы можете получить его как испорченный результат разыменования итератора контейнера. Перетащив std::begin и std::end в область, вы можете включить ADL для бесплатного начала и end функции, продолжая перехватывать функции-члены через стандартные функции.

Сначала несколько заголовков и вспомогательный класс:

#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','; };
}

Теперь мы пытаемся реализовать as_comma_list с включенной итерацией ADL и без каких-либо ограничений в отношении макета шаблона контейнера или строки.

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;
}

Примечание. В этом примере требуется, чтобы тип String также был итерируемым (что встречается очень часто), и у него есть вторая ветвь в цикле, которая может быть медленнее при работе с миллиардами строк здесь.

person Pixelchemist    schedule 15.09.2017
comment
Вау, спасибо за вклад! Это действительно очень помогает мне. Однако я не совсем уверен, как бы я назвал это? Я имею в виду, что я пытался установить что-то вроде std::vector<std::string> > = v{"Hello", "World!"}; as_comma_list<std::vector<std::string> >(v) без удачной компиляции. - person Carlos Brito; 16.09.2017
comment
Сообщение об ошибке заключается в том, что нет преобразования из std::vector<std::string> (aka 'vector<basic_string<char, char_traits<char>, allocator<char> > >') в std::__1::vector<std::__1::basic_string<char>, std::__1::allocator<std::__1::basic_string<char> > > && - person Carlos Brito; 16.09.2017
comment
Может быть, компилятор неправильно выводит шаблон? - person Carlos Brito; 16.09.2017
comment
Так что, конечно, компилятор выплюнул это... Я не заметил, что поместил реализацию в файл .cpp. Огромное спасибо за помощь! - person Carlos Brito; 16.09.2017
comment
@CarlosBrito: Просто попробуйте as_comma_list(v); без каких-либо параметров шаблона, так как он будет выведен. - person Pixelchemist; 16.09.2017

Попытка написать общий код таким образом обречена на провал, потому что в общем случае контейнеры нельзя описать как container<T, traits...>, подумайте о map<key, T, Compare, Allocator>, у которого есть value_type=pair<key, T>.

Вместо этого в C++ этот тип универсального программирования обычно выполняется через iterators (как и во всей стандартной библиотеке), например.

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;
}
person Walter    schedule 15.09.2017
comment
Как вы упомянули, я попробовал решение @max66 с таким контейнером, как map; логически я не могу описать контейнер STL, как пытался. Спасибо за помощь :-) - person Carlos Brito; 16.09.2017

Как насчет

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

?

Но вам нужно Traits?

Я полагаю, что вы можете упростить свой код следующим образом

template <template <typename...> class STLContainer, typename String> 
String as_comma_list(const STLContainer<String> &container)
person max66    schedule 15.09.2017
comment
Интересная концепция, пример ;-) - person Walter; 16.09.2017
comment
@Walter - интересно, не так ли :( ? И я использовал проверку орфографии. Спасибо! - person max66; 16.09.2017
comment
Хотя я должен спросить, ссылаясь на ответ @Walter. Будет ли это поймать все контейнеры STL? Я проверил это, и компилятор успешно сделал вывод, что STLContainer — это std::vector<std::string>, а String — это std::string. - person Carlos Brito; 16.09.2017
comment
@CarlosBrito - часть аргумента шаблона перехватывает почти весь контейнер STL (исключение: std::array, потому что второй параметр шаблона не является типом); но, вероятно, вы не хотите использовать их все одинаково; Я имею в виду: метод begin() для (в качестве примера) векторов и наборов возвращает итератор, указывающий на содержащийся (String) элемент; для карт (упорядоченных, неупорядоченных, одиночных или множественных) begin() возвращает итератор, указывающий на std::pair<T1, T2>, где T1 и T2 — аргументы первого и второго типа STLContainers. Смотрите также ответ Уолтера. - person max66; 16.09.2017