Fold expression for a parameter pack with comma operator: How to add additional parameters when expanding the pack?

huangapple go评论82阅读模式
英文:

Fold expression for a parameter pack with comma operator: How to add additional parameters when expanding the pack?

问题

  1. template<unsigned... Ns>
  2. constexpr auto concatWithSeparator(const char(&...s)[Ns])
  3. {
  4. return CTString<((0U + Ns) * 2 - 1U)>{(s, '|', ...)};
  5. }
英文:

I want to design a compile-time-string class CTString that can e.g. be constructed from a parameter pack of string literals. This works using a comma-fold-expression (for this toy example, I tried to avoid the use of any system headers to make it self-contained):

  1. template&lt;unsigned N&gt;
  2. struct CTString
  3. {
  4. char m_chars[N + 1U];
  5. template&lt;unsigned... Ns&gt;
  6. constexpr CTString(const char (&amp;...s)[Ns])
  7. {
  8. auto* p{ m_chars };
  9. ((p = CopyN_(s, Ns - 1U, p)), ...);
  10. *p = &#39;
    template&lt;unsigned N&gt;
  11. struct CTString
  12. {
  13. char m_chars[N + 1U];
  14. template&lt;unsigned... Ns&gt;
  15. constexpr CTString(const char (&amp;...s)[Ns])
  16. {
  17. auto* p{ m_chars };
  18. ((p = CopyN_(s, Ns - 1U, p)), ...);
  19. *p = &#39;\0&#39;;
  20. }
  21. // copy size characters and return one past last copy:
  22. constexpr char* CopyN_(const char* pFrom, unsigned size, char* pTo)
  23. {
  24. for (auto i{ 0U }; i &lt; size; ++i)
  25. *(pTo++) = *(pFrom++);
  26. return pTo;
  27. }
  28. };
  29. template&lt;unsigned... Ns&gt;
  30. constexpr auto concat(const char(&amp;...s)[Ns])
  31. {
  32. return CTString&lt;(0U + ... + (Ns - 1U))&gt;{s...};
  33. }
  34. constexpr auto cHelloWorld{ concat(&quot;Hello&quot;, &quot;World&quot;) };
  35. static_assert(cHelloWorld.m_chars[9] == &#39;d&#39;);
  36. static_assert(cHelloWorld.m_chars[10] == &#39;\0&#39;);
  37. &#39;;
  38. }
  39. // copy size characters and return one past last copy:
  40. constexpr char* CopyN_(const char* pFrom, unsigned size, char* pTo)
  41. {
  42. for (auto i{ 0U }; i &lt; size; ++i)
  43. *(pTo++) = *(pFrom++);
  44. return pTo;
  45. }
  46. };
  47. template&lt;unsigned... Ns&gt;
  48. constexpr auto concat(const char(&amp;...s)[Ns])
  49. {
  50. return CTString&lt;(0U + ... + (Ns - 1U))&gt;{s...};
  51. }
  52. constexpr auto cHelloWorld{ concat(&quot;Hello&quot;, &quot;World&quot;) };
  53. static_assert(cHelloWorld.m_chars[9] == &#39;d&#39;);
  54. static_assert(cHelloWorld.m_chars[10] == &#39;
    template&lt;unsigned N&gt;
  55. struct CTString
  56. {
  57. char m_chars[N + 1U];
  58. template&lt;unsigned... Ns&gt;
  59. constexpr CTString(const char (&amp;...s)[Ns])
  60. {
  61. auto* p{ m_chars };
  62. ((p = CopyN_(s, Ns - 1U, p)), ...);
  63. *p = &#39;\0&#39;;
  64. }
  65. // copy size characters and return one past last copy:
  66. constexpr char* CopyN_(const char* pFrom, unsigned size, char* pTo)
  67. {
  68. for (auto i{ 0U }; i &lt; size; ++i)
  69. *(pTo++) = *(pFrom++);
  70. return pTo;
  71. }
  72. };
  73. template&lt;unsigned... Ns&gt;
  74. constexpr auto concat(const char(&amp;...s)[Ns])
  75. {
  76. return CTString&lt;(0U + ... + (Ns - 1U))&gt;{s...};
  77. }
  78. constexpr auto cHelloWorld{ concat(&quot;Hello&quot;, &quot;World&quot;) };
  79. static_assert(cHelloWorld.m_chars[9] == &#39;d&#39;);
  80. static_assert(cHelloWorld.m_chars[10] == &#39;\0&#39;);
  81. &#39;);

Now I have an additional use case to insert a separator after each literal. How can I expand/fold the parameter pack to insert e.g. the literal &quot;|&quot; after each element of the pack?
This is my feeble attempt that fails because the expression (s, &quot;|&quot;)... does not work: The comma here just leads to the left operand being discarded:

  1. template&lt;unsigned... Ns&gt;
  2. constexpr auto concatWithSeparator(const char(&amp;...s)[Ns])
  3. {
  4. return CTString&lt;(0U + ... + Ns)&gt;{(s, &quot;|&quot;)...};
  5. }
  6. // Compilation error:
  7. constexpr auto cHelloCommaSeparated{ concatWithSeparator(&quot;Hello&quot;, &quot;World&quot;) };

I can work around this problem by introducing a helper class and having the compile-time-string also accept packs of the helper class in its constructors. But I was wondering whether there is neat idiom that I am missing.(I did read and re-read this great article, but to no avail:
C++20 idioms for parameter packs

The code that compiles is here:
Godbolt
Un-comment the last line to see how it fails.

答案1

得分: 4

  1. template<unsigned... Ns>
  2. constexpr auto concatWithSeparator(const char(&...s)[Ns])
  3. {
  4. return CTString<(0U + ... + Ns)>{concat(s, "|").m_chars...};
  5. }
英文:

A simple and pragmatic solution would be to re-use your concat function:

  1. template&lt;unsigned... Ns&gt;
  2. constexpr auto concatWithSeparator(const char(&amp;...s)[Ns])
  3. {
  4. return CTString&lt;(0U + ... + Ns)&gt;{concat(s, &quot;|&quot;).m_chars...};
  5. }

https://godbolt.org/z/hzv5qv6no.


If you want to know how to interleave the parameter pack with the seperator |, I don't think there is a simple solution. One option is to first create a tuple, whose elements are the parameter pack interleaved with the seperator:

  1. auto tup = std::tuple_cat(
  2. std::tuple&lt;const char(&amp;)[Ns], const char(&amp;)[2] &gt;(s, &quot;|&quot;)...
  3. );

As a next step, you would need to translate the tuple back to a parameter pack, which in turn can be passed as argument(s) for the CTString constructor.

You could do this by forwarding the tuple to a templated helper function using std::index_sequence. With C++20, you have templated lambdas so that the helper function could be a lambda defined in the body of concatWithSeparator that you immediately evaluate:

  1. template&lt;unsigned... Ns&gt;
  2. constexpr auto concatWithSeparator(const char(&amp;...s)[Ns])
  3. {
  4. auto tup = std::tuple_cat(
  5. std::tuple&lt;const char(&amp;)[Ns], const char(&amp;)[2] &gt;(s, &quot;|&quot;)...
  6. );
  7. return [&amp;]&lt;std::size_t ... Is&gt;(std::index_sequence&lt;Is...&gt;)
  8. {
  9. return CTString&lt;(0U + ... + Ns)&gt;{std::get&lt;Is&gt;(tup)...};
  10. }
  11. (
  12. std::make_index_sequence&lt;2*sizeof...(Ns)&gt;{}
  13. );
  14. }

https://godbolt.org/z/W1jhcrGc5


A slightly more readible version of the second solution would be to use std::apply:

  1. template&lt;unsigned... Ns&gt;
  2. constexpr auto concatWithSeparator(const char(&amp;...s)[Ns])
  3. {
  4. return std::apply(
  5. [&amp;](auto const&amp;... args){ return CTString&lt;(0U + ... + Ns)&gt;(args...); },
  6. std::tuple_cat(std::tuple&lt;const char(&amp;)[Ns], const char(&amp;)[2] &gt;{s, &quot;|&quot;}...)
  7. );
  8. }

https://godbolt.org/z/q3GshYx5a

答案2

得分: 1

你不能将一个包 arg0, .., argN 转换为 arg0, sep, .., argN, sep

作为替代,你可以添加额外的构造函数,以使包的每个元素执行额外的操作:

  1. struct SeparatorTag{};
  2. template<unsigned N>
  3. struct CTString
  4. {
  5. char m_chars[N + 1U];
  6. template<unsigned int NSep, unsigned... Ns>
  7. constexpr CTString(SeparatorTag, const char(&sep)[NSep], const char (&...s)[Ns])
  8. {
  9. auto* p{ m_chars };
  10. ((p = CopyN_(s, Ns - 1U, p), p = CopyN_(sep, NSep - 1, p)), ...);
  11. *p = '
    struct SeparatorTag{};
  12. template<unsigned N>
  13. struct CTString
  14. {
  15.     char m_chars[N + 1U];
  16.     template<unsigned int NSep, unsigned... Ns>
  17.     constexpr CTString(SeparatorTag, const char(&sep)[NSep], const char (&...s)[Ns])
  18.     {
  19.         auto* p{ m_chars };
  20.         ((p = CopyN_(s, Ns - 1U, p), p = CopyN_(sep, NSep - 1, p)), ...);
  21.         *p = '\0';
  22.     }
  23.     // ...
  24. };
  25. ';
  26. }
  27. // ...
  28. };

演示

英文:

You cannot transform a pack arg0, .., argN into arg0, sep, .., argN, sep.

As alternative, you might add extra constructor to make extra works by element of pack:

  1. struct SeparatorTag{};
  2. template&lt;unsigned N&gt;
  3. struct CTString
  4. {
  5. char m_chars[N + 1U];
  6. template&lt;unsigned int NSep, unsigned... Ns&gt;
  7. constexpr CTString(SeparatorTag, const char(&amp;sep)[NSep], const char (&amp;...s)[Ns])
  8. {
  9. auto* p{ m_chars };
  10. ((p = CopyN_(s, Ns - 1U, p), p = CopyN_(sep, NSep - 1, p)), ...);
  11. *p = &#39;
    struct SeparatorTag{};
  12. template&lt;unsigned N&gt;
  13. struct CTString
  14. {
  15. char m_chars[N + 1U];
  16. template&lt;unsigned int NSep, unsigned... Ns&gt;
  17. constexpr CTString(SeparatorTag, const char(&amp;sep)[NSep], const char (&amp;...s)[Ns])
  18. {
  19. auto* p{ m_chars };
  20. ((p = CopyN_(s, Ns - 1U, p), p = CopyN_(sep, NSep - 1, p)), ...);
  21. *p = &#39;\0&#39;;
  22. }
  23. // ...
  24. };
  25. &#39;;
  26. }
  27. // ...
  28. };

Demo

huangapple
  • 本文由 发表于 2023年4月17日 18:40:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76034271.html
  • c++
  • c++20
  • compile-time-type-checking
  • fold-expression
  • variadic-templates
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定