【问题标题】:Is it possible to fold only part of the pack with C++17 fold expressions?是否可以使用 C++17 折叠表达式仅折叠包的一部分?
【发布时间】:2020-01-25 00:13:54
【问题描述】:

我试图弄清楚如何使用 C++17 折叠表达式仅折叠可变参数模板包的一部分。 假设我想创建一个编译时“分隔字符串”,如"str1_str2_str3_....."

使用这样的代码可以很容易地完成(只是一个例子):

std::string result;

template<class... Strings>
ConcatString(Strings&... strs)
{
    (ConcatString(strs), ...);
}

template <class String>
void ConcatString(String& str)
{
     result += str + "_"
}

那我们就可以这样执行了

std::string s1 = "s1", s2 = "s2", s3 = "s3";
ConcatString(s1, s2, s3);
// result == "s1_s2_s3_"

如您所见,最后一个分隔符有问题。如果没有运行时检查,有什么办法可以避免这个问题?我可以想象的一种解决方案是仅折叠 (N - 1) 个 args 并“手动”连接最后一个。

【问题讨论】:

    标签: c++ c++17 variadic-templates fold-expression


    【解决方案1】:

    您可以递归调用 ConcatString 并使用 constexpr if 来避免运行时检查

    template<typename String, typename... Strings>
    void ConcatString(String& str, Strings&... strs) {
        if constexpr(sizeof...(strs) == 0) {
            result += str;
        } else {
            result += str + "_";
            ConcatString(strs...);
        }
    }
    

    【讨论】:

      【解决方案2】:

      是否可以使用 C++17 折叠表达式仅折叠包的一部分?

      不,折叠表达式将折叠整个包。但是,我们可以做一些技巧来实现我们需要的。

      在这里,您的Concat 函数可以简化为使用单个二进制折叠。

      template<class String, class... Strings>
      std::string Concat(const std::string& delimiter, const String& str, const Strings&... strs)
      {
           return (str + ... + (delimiter + strs));
      }
      

      用法:

      int main()
      {
          std::cout << Concat(",", "a") << std::endl;
          std::cout << Concat(",", "a", "b") << std::endl;
          std::cout << Concat(",", "a", "b", "c") << std::endl;
      }
      

      输出:

      一个
      a,b
      a,b,c

      Live Demo

      这里的技巧是我们将参数包拆分为一个单数“头”(str)和一个可变参数“尾”(strs)。通过这种方式,我们让函数参数列表从包中提取第一个元素。 (很多 C++11 风格的模板元编程使用了这个技巧)。

      另一种方法是为我们的参数包创建一组索引 0、1、...、N,然后对于我们的折叠逻辑,我们可以为第 0、第 N 甚至任意元素做一些特殊的事情.您可以在 pretty print tuple 问题上找到这种方法的各种变体。 在 C++20 中,由于 C++20 中的模板 lambda,我们可以将所有逻辑移动到一个方法中,如下所示:

      template<class... Strings>
      std::string Concat(const std::string& delimiter, const Strings&... strs)
      {
          return [&delimiter]<class Tup, size_t... I> (const Tup& tuple, std::index_sequence<I...>)
          {
              return (std::string{} + ... + (I == 0 ? std::get<I>(tuple) : delimiter + std::get<I>(tuple)));
          }(std::tie(strs...), std::make_index_sequence<sizeof...(strs)>{});
      }
      

      Demo 2

      那个丑陋的语法是我创建了一个 lambda 并在一个语句中调用它。您可以改为通过std::invoke 调用它使其更具可读性。

      请注意,我们使用索引检查是否打印分隔符。

      让我们使用索引检查技巧,只用分隔符连接彼此:

      template<class... Strings>
      std::string ConcatEveryOther(const std::string& delimiter, const Strings&... strs)
      {
          return [&delimiter]<class Tup, size_t... I> (const Tup& tuple, std::index_sequence<I...>)
          {
              return (std::string{} + ... + (I % 2 == 0 ? std::get<I>(tuple) : delimiter + std::get<I>(tuple)));
          }(std::tie(strs...), std::make_index_sequence<sizeof...(strs)>{});
      }
      

      现在std::cout &lt;&lt; ConcatEveryOther(",", "a", "b", "c", "d", "e", "f", "g") &lt;&lt; std::endl; 会给我们一个像

      这样的输出

      a,bc,de,fg

      Demo 3

      【讨论】:

      • 因为它与 Matthew Reddington 的答案相同(在“编辑”之后)。除了“std::string{"_"}”详细信息。
      【解决方案3】:

      怎么样:

      template<typename Last>
      std::string concat(Last last) {
          return last;
      }
      
      template<typename First, typename ...Rest>
      std::string concat(First first, Rest ...rest) {
          return first + '_' + concat(rest...);
      }
      
      std::string s1{"foo"}, s2{"bar"}, s3{"baz"};
      std::cout << concat(s1, s2, s3); // Prints "foo_bar_baz"
      

      编辑:我使函数更简单:

      template <typename First, typename... Rest>
      std::string concat(First first, Rest... rest) {
          return (first + ... + ('_' + rest));
      }
      

      【讨论】:

      • 不错 (+1) 但我给你一个建议:使用std::string{"_"} 而不是'_';否则,调用一些char const [someLen](例如:concat("s1", "s2", "s3"))会出现错误,因为没有定义operator+()。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-08
      • 2020-02-21
      • 1970-01-01
      • 2019-10-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多