在C++中,可以使用可变模板参数来检索类型的内部类型。

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

Is there a way to retrieve the inner types of a type using variadic templates in C++?

问题

假设我有一个使用可变模板的类型:

  1. template <typename... Args>
  2. struct Outer
  3. {
  4. // using Inner = something that captures ...Args ???;
  5. }

我如何定义Inner,以便我以后可以在其他一些模板代码中使用它,例如:

  1. // ... 一些接受其他模板参数的现有函数
  2. template <typename... Args2>
  3. foo(Args2 ...)
  4. {
  5. // ...
  6. };
  7. // ... 在某处定义一些特定的Outers,例如但不限于
  8. using Specific1 = Outer<int, double, bool>;
  9. using Specific2 = Outer<std::string>;
  10. // ...
  11. foo(Specific1::Inner... args)
  12. // ...
  13. foo(Specific2::Inner... args)

我主要关注的是C++17,但也愿意学习在任何其他版本的C++中如何实现。理想情况下,我希望在不使用std::tuple的情况下实现这一点。

一个最小可复现的示例:

  1. template <typename... Args>
  2. struct Outer
  3. {
  4. using Cb = std::function<void(Args...)>;
  5. //using Inner = Args; // 这个会报错:"parameter pack must be expanded in this context"
  6. //using Inner = Args...; // 这个会报错:"parameter pack cannot be expanded in this context"
  7. template<typename CbIn>
  8. void store(CbIn&& cb)
  9. {
  10. mCb = std::forward<CbIn>(cb);
  11. }
  12. void call(Args... args) noexcept
  13. {
  14. mCb(args...);
  15. }
  16. // cb here accepts Args... and returns OtherOuter* (specialized with different Args)
  17. template<typename OtherOuter, typename CbIn>
  18. void foo(CbIn&& cb)
  19. {
  20. store([cb{ std::forward<CbIn>(cb)}](Args... args)
  21. {
  22. OtherOuter * other = cb(std::forward<Args>(args)...);
  23. other->store([](/*OtherOuter::Inner*/ ... otherArgs) // 如果不是OtherOuter::Inner,这里应该放什么?
  24. {
  25. // 对otherArgs做一些操作
  26. ([&]
  27. {
  28. std::cout << "second " << otherArgs << std::endl;
  29. } (), ...);
  30. });
  31. std::cout << "first " << other->mFlag << std::endl;
  32. });
  33. }
  34. Cb mCb;
  35. bool mFlag = false;
  36. };

使用这个示例:

  1. using OuterIntBool = Outer<int, bool>;
  2. using OuterString = Outer<std::string>;
  3. // 正常工作
  4. {
  5. OuterIntBool outerIntBool;
  6. outerIntBool.store([](int i, bool b)
  7. {
  8. bool isValid = i > 0 && b;
  9. assert(isValid);
  10. });
  11. outerIntBool.call(1, true);
  12. }
  13. // 不工作
  14. {
  15. OuterIntBool outerIntBool;
  16. OuterString otherString;
  17. outerIntBool.foo<OuterString>([&otherString](int/* i*/, bool b)
  18. {
  19. otherString.mFlag = b;
  20. return &otherString;
  21. });
  22. outerIntBool.call(1, true);
  23. otherString.call("bar");
  24. }
英文:

Assume I have a type using variadic templates:

  1. template &lt;typename... Args&gt;
  2. struct Outer
  3. {
  4. // using Inner = something that captures ...Args ???;
  5. }

How could I define Inner such that I could later use it elsewhere in some templated code like:

  1. // ... some existing function accepting potentially other template arguments
  2. template &lt;typename... Args2&gt;
  3. foo(Args2 ...)
  4. {
  5. // ...
  6. };
  7. // ... somewhere I define some specialized Outers, as examples but not limited to
  8. using Specific1 = Outer&lt;int, double, bool&gt;;
  9. using Specific2 = Outer&lt;std::string&gt;;
  10. // ...
  11. foo(Specific1::Inner... args)
  12. // ...
  13. foo(Specific2::Inner... args)

I am mainly interested on C++17, but open to learn however it could be done in any other version of C++. Ideally I would like to achieve this without joggling around with std::tuple.

A minimal reproducible example:

  1. template &lt;typename... Args&gt;
  2. struct Outer
  3. {
  4. using Cb = std::function&lt;void(Args...)&gt;;
  5. //using Inner = Args; // this one complains &quot;parameter pack must be expanded in this context&quot;
  6. //using Inner = Args...; // this one complains &quot;parameter pack cannot be expanded in this context&quot;
  7. template&lt;typename CbIn&gt;
  8. void store(CbIn&amp;&amp; cb)
  9. {
  10. mCb = std::forward&lt;CbIn&gt;(cb);
  11. }
  12. void call(Args... args) noexcept
  13. {
  14. mCb(args...);
  15. }
  16. // cb here accepts Args... and returns OtherOuter* (specialized with different Args)
  17. template&lt;typename OtherOuter, typename CbIn&gt;
  18. void foo(CbIn&amp;&amp; cb)
  19. {
  20. store([cb{ std::forward&lt;CbIn&gt;(cb)}](Args... args)
  21. {
  22. OtherOuter * other = cb(std::forward&lt;Args&gt;(args)...);
  23. other-&gt;store([](/*OtherOuter::Inner*/ ... otherArgs) // what to put here if not OtherOuter::Inner ???
  24. {
  25. // do something with otherArgs
  26. ([&amp;]
  27. {
  28. std::cout &lt;&lt; &quot;second &quot; &lt;&lt; otherArgs &lt;&lt; std::endl;
  29. } (), ...);
  30. });
  31. std::cout &lt;&lt; &quot;first &quot; &lt;&lt; other-&gt;mFlag &lt;&lt; std::endl;
  32. });
  33. }
  34. Cb mCb;
  35. bool mFlag = false;
  36. };

And using the example:

  1. using OuterIntBool = Outer&lt;int, bool&gt;;
  2. using OuterString = Outer&lt;std::string&gt;;
  3. // works
  4. {
  5. OuterIntBool outerIntBool;
  6. outerIntBool.store([](int i, bool b)
  7. {
  8. bool isValid = i &gt; 0 &amp;&amp; b;
  9. assert(isValid);
  10. });
  11. outerIntBool.call(1, true);
  12. }
  13. // does not work
  14. {
  15. OuterIntBool outerIntBool;
  16. OuterString otherString;
  17. outerIntBool.foo&lt;OuterString&gt;([&amp;otherString](int/* i*/, bool b)
  18. {
  19. otherString.mFlag = b;
  20. return &amp;otherString;
  21. });
  22. outerIntBool.call(1, true);
  23. otherString.call(&quot;bar&quot;);
  24. }

答案1

得分: 3

如何“保存”可变模板参数?

直接的方式,就像我们对待常规模板参数一样,可行:

  1. template <typename T>
  2. struct s1
  3. {
  4. using type = T; // OK
  5. };
  6. template <typename... Ts>
  7. struct s2
  8. {
  9. using types = Ts...; // KO
  10. };

但是,有几种方法可以“保存”可变参数:

  • 使用某种可变类型来保存它,例如std::tuple或您自己的type_list

    1. template <typename... Ts> struct type_list {};
    2. template <typename... Args>
    3. struct Outer
    4. {
    5. using type1s = std::tuple<Args...>;
    6. using type2s = type_list<Args...>;
    7. };
  • 一种解构的方式:

    1. template <typename... Args>
    2. struct Outer
    3. {
    4. constexpr type_size = sizeof...(Args);
    5. template <std::size_t I>
    6. using type = std::tuple_element_t<I, std::tuple<Args...>>;
    7. };
  • 使用外部类型特性(与上述任何结果都可以)。例如解构的方式示例:

    1. template <typename T>
    2. constexpr std::size_t outer_size;
    3. template <typename... Ts>
    4. constexpr std::size_t outer_size<Outer<Ts...>> = sizeof...(Ts);
    5. template <std::size_t I, typename T>
    6. struct arg_element;
    7. template <std::size_t I, typename... Ts>
    8. struct arg_element<I, Outer<Ts...>> {
    9. using type = std::tuple_element_t<I, std::tuple<Ts...>>;
    10. };

如何使用保存的参数?

std::index_sequence可能会有所帮助:

  1. template <typename Outer>
  2. void get_printer(const Outer&)
  3. {
  4. []<std::size_t... Is>(std::index_sequence<Is...>){
  5. // typename Outer::template type<Is>... is not equivalent to the Ts...
  6. // std::tuple_element_t<Is, typename Outer::types>... for tuple case
  7. return [](const typename Outer::template type<Is>&... args){
  8. (std::cout << args), ...);
  9. };
  10. }(std::make_index_sequence<Outer::size_type>()); // tuple_size<Outer::types>;
  11. }
英文:

> How to "save" variadic template parameter?

The direct way, as we do with regular template argument is not possible:

  1. template &lt;typename T&gt;
  2. struct s1
  3. {
  4. using type = T; // OK
  5. };
  6. template &lt;typename... Ts&gt;
  7. struct s2
  8. {
  9. using types = Ts...; // KO
  10. };

but, they are several ways to "save" variadic arguments:

  • Use some variadic type to keep it std::tuple or your own type_list

    1. template &lt;typename... Ts&gt; struct type_list {};
    2. template &lt;typename... Args&gt;
    3. struct Outer
    4. {
    5. using type1s = std::tuple&lt;Args...&gt;;
    6. using type2s = type_list&lt;Args...&gt;;
    7. };
  • A destructured way:

    1. template &lt;typename... Args&gt;
    2. struct Outer
    3. {
    4. constexpr type_size = sizeof...(Args);
    5. template &lt;std::size_t I&gt;
    6. using type = std::tuple_element_t&lt;I, std::tuple&lt;Args...&gt;&gt;;
    7. };
  • an external type_trait (with any of the above result). so destructured way example:

    1. template &lt;typename T&gt;
    2. constexpr std::size_t outer_size;
    3. template &lt;typename... Ts&gt;
    4. constexpr std::size_t outer_size&lt;Outer&lt;Ts...&gt;&gt; = sizeof...(Ts);
    5. template &lt;std::size_t I, typename T&gt;
    6. struct arg_element;
    7. template &lt;std::size_t I, typename... Ts&gt;
    8. struct arg_element&lt;I, Outer&lt;Ts...&gt;&gt; {
    9. using type = std::tuple_element_t&lt;Is, std::tuple&lt;Ts...&gt;&gt;;
    10. };

> How to use saved parameters?

std::index_sequence might help:

  1. template &lt;typename Outer&gt;
  2. void get_printer(const Outer&amp;)
  3. {
  4. []&lt;std::size_t... Is&gt;(std::index_sequence&lt;Is...&gt;){
  5. // typename Outer::template type&lt;Is&gt;... is not equivalent to the Ts...
  6. // std::tuple_element_t&lt;Is, typename Outer::types&gt;... for tuple case
  7. return [](const typename Outer::template type&lt;Is&gt;&amp;... args){
  8. (std::cout &lt;&lt; args), ...);
  9. };
  10. }(std::make_index_sequence&lt;Outer::size_type&gt;()); // tuple_size&lt;Outer::types&gt;
  11. }

答案2

得分: 1

你尝试过 other->store([](auto&& ... otherArgs) 吗?

英文:

Did you try other-&gt;store([](auto&amp;&amp; ... otherArgs)?

huangapple
  • 本文由 发表于 2023年8月11日 15:16:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76881423.html
匿名

发表评论

匿名网友

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

确定