可变参数模板的未声明标识符

Undeclared identifier for Variadic Template

我仍在学习如何使用可变参数模板。基本上我想要做的是采用包含 String 类型元素的 STLContainer。 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 的答案,因为它似乎是最 "generic proof" 的解决方案,并提供了对我的代码的洞察力。但是,我想说@Walter 的回答同样好。虽然@max66 的回答是最简单的解决问题的方法,但最初的问题是我试图错误地描述一个 STL 容器。

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)

您的代码必须如下所示:

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

但让我们看看您的代码的含义:

  1. 类型STLContainer必须采用模板参数。如果我写一个 class simplestringvector(我知道这不是通用的,但没有人能阻止我 :))你的代码对我不起作用。

  2. STLContainer 类型必须提供 begin()end() 成员。 (没有自由函数;没有 std::beginstd::end

  3. String 类型必须提供一个 pop_back() 成员。

  4. String 类型必须有 operator+= 定义,它必须能够处理包含 char 的字符串文字(没有 wchar_t,没有char16_t, ...).

  5. 以及其他在这里不是问题的问题。

函数可以更通用:

没有保修,因为我很累,但无论如何...

如果您的代码无论如何都要求类型是可迭代的,那么您不需要首先知道 String 的类型。您可以将其作为取消引用容器迭代器的衰减结果来获取。通过将 std::beginstd::end 拖到作用域中,您可以启用 ADL 免费开始和结束函数,同时仍然通过标准函数捕获成员函数。

首先是一些 headers 和一个帮手 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','; };
}

现在我们尝试实现 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 类型也是可迭代的(这很常见)并且它在循环中有第二个分支,在此处处理数十亿个字符串时可能会更慢。

尝试用这种方式编写通用代码注定会失败,因为一般容器不能描述为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;
}