【问题标题】:How to make grouped or paired fold of parameter pack?如何进行参数包的分组或成对折叠?
【发布时间】:2023-04-10 23:00:01
【问题描述】:
template<class Msg, class... Args>
std::wstring descf(Msg, Args&&... args) {
    std::wostringstream woss;

    owss << Msg << ". " << ... << " " << args << ": '" << args << "' ";//not legal at all

    //or

    owss << Msg << ". " << args[0] << ": '" << args[1] << "'  " << args[2] << ": '" << args[3] << "' "; //... pseudo code, and so on...
}

我知道我可以只使用对列表或类似的东西,但我感兴趣的是如何在保持函数语法的同时做到这一点:

const auto formatted = descf(L"message", "arg1", arg1, "arg2", arg2);

【问题讨论】:

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


    【解决方案1】:

    您可以使用折叠表达式!它不是最漂亮的*,但它比所有非折叠解决方案都要短:

    template<class T, class ... Args>
    std::wstring descf(T msg, Args&&... args) {
        std::wostringstream owss;
        owss << msg << ". ";
    
        std::array<const char*, 2> tokens{": '", "' "};
        int alternate = 0;
        ((owss << args << tokens[alternate], alternate = 1 - alternate), ...);
    
        return owss.str();
    }
    

    带有示例输出的演示:https://godbolt.org/z/Gs8d2x

    我们对逗号运算符执行折叠,其中每个操作数是一个 args 和交替标记的输出,加上切换标记索引(后两者与另一个逗号运算符组合)。

    *对于熟悉折叠表达式(和逗号运算符)的读者来说,这可能是“最好的”代码,但对其他人来说,这完全是胡言乱语,所以请自行判断是否要将其强加于您的代码库。

    【讨论】:

    • 我想这也可以与 bool 一起使用(如果只需要配对)ala。 : b ^= 真;然后也许是三元运算符 (b ? ": '", " : "' ")
    • @darune 当然,还有其他表达交替的方式。我决定将输出/交替逻辑与实际令牌值分开,数组很好地完成了这一点。我不喜欢索引时从boolint 的隐式转换,所以我使用实际的int 来切换状态。并且前后缀 ++ 需要额外的心理周期来验证(至少对我而言),而单独的 1 - 不会被误读。简而言之,我尽量保持它的可读性,但这当然取决于个人喜好(或适用的风格指南)。 max66 更浓缩了它。
    • 使用std::array 代替原生数组似乎是一个毫无意义的复杂问题。
    • @Deduplicator 我强烈反对,因为我发现std::array&lt;const char*, 2&gt;const char** 更具可读性。但同样,这是我的最好的关于一些相当晦涩的语法的可读性,你可以在你自己的代码中做你喜欢的事情。我所能做的就是给你我认为可读的数据点。
    【解决方案2】:

    这很容易通过几个遵循以下模式的辅助函数来实现。

    void helper() {}
    
    template <class T1, class T2, class ... T>
    void helper(T1 t1, T2 t2, T ... t)
    {
         do_single_pair(t1, t2);
         helper(t...);
    }
    

    这不是折叠表达式,但最终结果是相同的。

    【讨论】:

    • 模板递归深度会与折叠表达式不同吗?还是一样
    • @darune 折叠表达式没有固有的递归......折叠表达式只是正式扩展为某个表达式(在可变参数模板的特定实例中)。
    【解决方案3】:

    我想您可以尝试使用索引和三元运算符。

    如下

    template <typename ... Args>
    std::wstring descf (std::wstring const & Msg, Args && ... args)
     {
       std::wostringstream woss;
    
       int i = 0;
    
       ((woss << Msg << ". "), ... ,(woss << args << (++i & 1 ? ": '" : "' ")));
    
       return woss.str();
     }
    

    【讨论】:

    • @MaxLanghof 这具有易于扩展到更多分隔符的优点(?)。
    • @Deduplicator 我不明白你指的是什么?你能解释一下吗?
    • @Deduplicator - 我不清楚“扩展到更多分隔符”是什么意思......无论如何......这个解决方案与公认的解决方案非常相似;我不认为它或多或少是可扩展的。我想这有点(一点点!也许编译器以同样的方式优化)更轻,因为避免使用 std::array (无论如何,这是一个轻量级)但是(所以我认为最好接受的答案)是可读性较差。
    【解决方案4】:

    下面的代码应该可以解决问题。参数包在初始化列表中展开。

    #include <string>
    #include <iostream>
    #include <sstream>
    #include <vector>
    
    template <typename...Args>
    std::string descf(std::string msg, Args &&... args)
    {
       auto argumentsVector = std::vector<std::string>{args...};
    
       std::stringstream ss;
       ss << msg << ". ";
    
       for (auto i = std::size_t{0}; i < argumentsVector.size() - 1; ++i)
          ss << argumentsVector[i] << ": '" << argumentsVector[i+1] << "' ";
    
       auto result = ss.str();
       if (!argumentsVector.empty())
           result.pop_back();
       return result;
    }
    
    int main()
    {
       std::cout << descf("message", "arg1", "1", "arg2", "2") << std::endl;
    }
    

    【讨论】:

    • 这要求所有args 都可以转换为std::strings。
    • @walnut,没错。如果这不是一个要求,那么你将不得不折叠表达式/递归
    【解决方案5】:

    std::index_sequence:

    template <class Msg, class... Pairs>
    std::wstring descf_pair(const Msg& msg, const Pairs&... pairs)
    {
        std::wstringstream woss;
    
        woss << msg << ". ";
        auto sep = L"";
        ((woss << sep << std::get<0>(pairs) << L": '"
                      << std::get<1>(pairs) << L"'", sep = L"  "), ...);
        return woss.str();
    }
    
    template <class Msg, std::size_t... Is, class Tuple>
    decltype(auto) descf_impl(const Msg& msg, std::index_sequence<Is...>, Tuple&& t)
    {
        return descf_pair(msg, std::tie(std::get<2 * Is>(t), std::get<2 * Is + 1>(t))...);
    }
    
    template <class Msg, typename ... Ts>
    std::wstring descf(const Msg& msg, const Ts&... ts)
    {
        static_assert(sizeof...(Ts) % 2 == 0);
    
        return descf_impl(msg,
                          std::make_index_sequence<sizeof...(Ts) / 2>(),
                          std::tie(ts...));
    }
    

    Demo

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-21
      • 1970-01-01
      • 1970-01-01
      • 2022-08-20
      • 1970-01-01
      • 2014-03-10
      • 2011-03-27
      • 2018-01-06
      相关资源
      最近更新 更多