【问题标题】:Generalised function template for standard library containers标准库容器的通用函数模板
【发布时间】:2013-12-18 12:18:15
【问题描述】:

我正在尝试编写一个简单的通用函数来迭代容器元素。每个元素都被转换为std::string(无论如何)并存储在另一个地方。基本版本很简单:

template<class Container>
void ContainerWork(const Container& c)
{
    for(const auto& elem : c) {
        /* convert to string and store*/
    }
}

然后有必要为具有值类型std::string 的容器添加专门化,并将代码转换为:

template<typename T, template<typename, typename> class Container, class Allocator>
void ContainerWork(Container<T, Allocator> c)
{
    for(const T& elem : c) {
        /* convert to string and store*/
    }
}

template<template<typename, typename> class Container, class Allocator>
void ContainerWork(Container<std::string, Allocator> c)
{
    for(const std::string& elem : c) {
        /* frame elem in quotes*/
    }
}

效果很好,但现在我只能使用已排序的容器(vectorlist 等),但我还想使用 setunordered_set。任何想法如何在没有 4 个参数的容器的“复制粘贴”实现的情况下做到这一点?我正在尝试与decltype(Container)::value_type 一起玩,但没有运气。

我可能会使用 c++11 的大部分功能(编译器 - VS2012 或 GCC 4.8.x)

【问题讨论】:

    标签: c++ templates c++11 stl


    【解决方案1】:

    这就是为什么标准库的所有算法都适用于迭代器而不是容器的原因。

    您可以更改您的核心功能以使用迭代器而不是容器。这将需要部分专业化,而函数模板不存在这种情况,因此我们将使用委托到类技巧:

    template <typename It>
    void DoIteratorWork(It start, It end)
    {
      DoIteratorWork_Impl<It, typename std::iterator_traits<It>::value_type>::call(start, end);
    }
    
    template <typename It, typename ValueType>
    struct DoIteratorWork_Impl
    {
      static void call(It start, It end)
      {
        for (; start != end; ++start) {
          // operate on *it
        }
      }
    };
    
    template <typename It>
    struct DoIteratorWork_Impl<It, std::string>
    {
      static void call(It start, It end)
      {
        for (; start != end; ++start) {
          // operate on *it
        }
      }
    };
    

    如果你真的想要,你可以围绕它创建一个包装器:

    template <class Container>
    void DoContainerWork(const Container& c)
    {
      using std::begin; using std::end; // enable ADL of begin and end
      return DoIteratorWork(begin(c), end(c));
    }
    

    【讨论】:

    • 这很优雅。我建议将inline 修饰符添加到DoIteratorWork 函数中。
    • @DarkWanderer 如果您认为您的编译器会注意它作为优化提示(我猜大多数人不会),请随意这样做。但由于它是一个模板,它在语言方面的影响为 0。
    • Visual Studio 编译器具有“仅 __inline”的内联设置。是的,我认为编译器在启用此功能时确实会查看 inline 关键字。见msdn.microsoft.com/en-us/library/z8y1yy88.aspx
    • @DarkWanderer 好的,很公平。尽管如此,我还是会考虑使用此设置来优化构建,但效果并不理想;-)
    • It::value_type 是错误的。使用std::iterator_traits&lt;It&gt;::value_type。与c.begin() 相同,应该是using std::begin; begin(c)——总是测试迭代器代码,看看它是否适用于原始C 数组,你会发现一堆这样的错误。 (如果可以的话,不要直接使用std::begin,所以可以找到带有ADL begin 方法的3rd 方容器)
    【解决方案2】:

    你可以使用variadic-templates,类似

    template<typename T, template<typename...> class Container, typename... Args>
    void ContainerWork(Container<T, Args...> c)
    {
    }
    
    template<template<typename...> class Container, typename... Args>
    void ContainerWork(Container<std::string, Args...> c)
    {
    }
    

    也许你当然可以使用简单的调度

    template<typename Container>
    void ContainerWork(Container c, 
    typename std::enable_if<!std::is_same<typename Container::value_type,
    std::string>::value>::type* = 0)
    {
    }
    
    template<typename Container>
    void ContainerWork(Container c,
    typename std::enable_if<std::is_same<typename Container::value_type,
    std::string>::value>::type* = 0)
    {
    }
    

    但无论如何,如果差异仅在于调用转换函数 - 您可以简单地为 Tstring 重载它,string 的版本将简单地返回此字符串。

    您还可以将 SFINAE 与 C++11 decltype 功能一起使用

    template<typename Container>
    auto ContainerWork(Container c) -> 
    decltype(c.begin(),
    typename std::enable_if<!std::is_same<typename Container::value_type,
    std::string>::value, void>::type())
    {
    }
    
    template<typename Container>
    auto ContainerWork(Container c) ->
    decltype(c.begin(),
    typename std::enable_if<std::is_same<typename Container::value_type,
    std::string>::value, void>::type())
    {
    }
    

    【讨论】:

    • 不计算... err... 在VS2012下编译
    • @DarkWanderer 使用 VS2013 或更高版本的可变参数模板。
    • @rubenvb:OP提到了VS2012,因此评论
    • 我有没有在任何地方提到这是一个糟糕的答案?不,我只是向以后偶然发现这个答案的人发出警告。
    • 我正在寻找更好的方法。 VS2012 不支持 VT,但如果这适用于 VS2013 和 gcc 4.8,我认为这可能是最好的解决方案。无论如何谢谢:)
    【解决方案3】:
    // implement the bodies as functors to allow for partial
    // specialization for value_type:
    template <class value_type>
    struct DoContainerWorkImpl {
      template<typename Container>
      void operator()(const Container& c) const
      {
        for (auto&& x : c) {
          // code
        }
      }
    };
    template <>
    struct DoContainerWorkImpl<std::string> {
      template<typename Container>
      void operator()(const Container& c) const
      {
        for (std::string const& x : c) {
          // code
        }
      }
    };
    
    template <class Container>
    void DoContainerWork(const Container& c)
    {
      using std::begin; // enable ADL of begin
      // extract the iterator for the container c:
      typedef typename std::decay<decltype( std::begin(c) )>::type iterator;
      // extract the value type of the container:
      typedef typename std::iterator_traits<iterator>::value_type value_type;
      DoContainerWorkImpl<value_type>()( c );
    }
    

    另一种方法是让算法成为一回事,并确定我们是否要使用特征类或其他“本地”技术在算法主体内调用转换。

    这是对这种方法的尝试:

     // convert non-strings:
     template<typename T>
     std::string as_string( T&& t )
     {
       // converting code goes here
     }
     // block dangling rvalues:
     std::string as_string( std::string&& s ) { return std::move(s); }
     // pass lvalues through:
     std::string const& as_string( std::string const& s ) { return s; }
     std::string const& as_string( std::string& s ) { return s; }
    

    现在,我们在您的循环体中执行此操作:

    for(auto&& elem : c) {
      std::string const& s = as_string(std::forward<decltype(elem)>(elem));
      // code using s as a string
    }
    

    如果容器是 std::string 的容器,则编译为 noop(或者,最坏的情况是几次移动),如果不是,则转换代码。

    分配给引用的临时对象的生命周期延长保留任何临时生成的std::string,迭代器的左值生命周期使“迭代器返回引用的字符串容器”生命周期足够长。

    【讨论】:

    • 他想在字符串中添加引号,所以std::string as_string(std::string) 不仅仅是一个副本。否则,这是我认为最好的解决方案。循环遍历元素不应该被复制(特别是如果您也必须在循环中复制围绕转换的代码)并且您不应该假设您将收到的容器将被模板化,第一个参数是您的 value_type。
    • @GuyGreer 啊,我错过了——我以为他试图避免不必要的 std::string 副本!同样的计划也适用于这种情况:您执行 as_stringstd::string s = quote_if_string(elem) 并让其他所有操作都忽略 elem 的类型。
    • @Yakk 是的,我的意思是“一些使用字符串的工作”,例如引用。在这种情况下,我们是否需要 as_string 的“完美转发”? elemin for 有没有机会成为 rvalue
    • @ShaKeSPeaR 一系列输入迭代器可以按值迭代(临时),否则将是您代码的有效参数。我只是倾向于编写比它需要的更通用的代码。
    • @Yakk 我问是因为我有一段类似的代码(许多to_string 用于用户类型)并使用像to_stirng(const T&amp; val) 这样的签名。现在我考虑切换到T&amp;&amp;...
    【解决方案4】:

    据我了解,您可以将std::for_each 与多态函子一起使用:

    namespace detail {
    
        template<typename T>
        std::string do_conversion(const T& item) {
            // convert to string
        }
    
        template<>
        std::string do_conversion<std::string>(const std::string& item) {
            // frame in quotes
        }
    }
    
    struct convert_to_string {
        template<typename T>
        void operator()(const T& item) {
            // delegate to free function in namespace scope, because otherwise we 
            // cannot specialize
            std::string result = detail::do_conversion(item);
        }
    };
    
    // in calling code
    
    std::for_each(std::begin(container), std::end(container), convert_to_string{});
    

    这对你有用吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-03
      • 1970-01-01
      • 2014-09-13
      • 1970-01-01
      • 1970-01-01
      • 2022-12-05
      相关资源
      最近更新 更多