英文:
Is there a way to retrieve the inner types of a type using variadic templates in C++?
问题 {#heading}
假设我有一个使用可变模板的类型:
template <typename... Args>
struct Outer
{
// using Inner = something that captures ...Args ???;
}
我如何定义Inner
,以便我以后可以在其他一些模板代码中使用它,例如:
// ... 一些接受其他模板参数的现有函数
template <typename... Args2>
foo(Args2 ...)
{
// ...
};
// ... 在某处定义一些特定的Outers,例如但不限于
using Specific1 = Outer<int, double, bool>;
using Specific2 = Outer<std::string>;
// ...
foo(Specific1::Inner... args)
// ...
foo(Specific2::Inner... args)
我主要关注的是C++17,但也愿意学习在任何其他版本的C++中如何实现。理想情况下,我希望在不使用std::tuple
的情况下实现这一点。
一个最小可复现的示例:
template <typename... Args>
struct Outer
{
using Cb = std::function<void(Args...)>;
//using Inner = Args; // 这个会报错:"parameter pack must be expanded in this context"
//using Inner = Args...; // 这个会报错:"parameter pack cannot be expanded in this context"
template<typename CbIn>
void store(CbIn&& cb)
{
mCb = std::forward<CbIn>(cb);
}
void call(Args... args) noexcept
{
mCb(args...);
}
// cb here accepts Args... and returns OtherOuter* (specialized with different Args)
template<typename OtherOuter, typename CbIn>
void foo(CbIn&& cb)
{
store([cb{ std::forward<CbIn>(cb)}](Args... args)
{
OtherOuter * other = cb(std::forward<Args>(args)...);
other->store([](/*OtherOuter::Inner*/ ... otherArgs) // 如果不是OtherOuter::Inner,这里应该放什么?
{
// 对otherArgs做一些操作
([&]
{
std::cout << "second " << otherArgs << std::endl;
} (), ...);
});
std::cout << "first " << other->mFlag << std::endl;
});
}
Cb mCb;
bool mFlag = false;
};
使用这个示例:
using OuterIntBool = Outer<int, bool>;
using OuterString = Outer<std::string>;
// 正常工作
{
OuterIntBool outerIntBool;
outerIntBool.store([](int i, bool b)
{
bool isValid = i > 0 && b;
assert(isValid);
});
outerIntBool.call(1, true);
}
// 不工作
{
OuterIntBool outerIntBool;
OuterString otherString;
outerIntBool.foo<OuterString>([&otherString](int/* i*/, bool b)
{
otherString.mFlag = b;
return &otherString;
});
outerIntBool.call(1, true);
otherString.call("bar");
}
英文:
Assume I have a type using variadic templates:
template <typename... Args>
struct Outer
{
// using Inner = something that captures ...Args ???;
}
How could I define Inner
such that I could later use it elsewhere in some templated code like:
// ... some existing function accepting potentially other template arguments
template <typename... Args2>
foo(Args2 ...)
{
// ...
};
// ... somewhere I define some specialized Outers, as examples but not limited to
using Specific1 = Outer<int, double, bool>;
using Specific2 = Outer<std::string>;
// ...
foo(Specific1::Inner... args)
// ...
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:
template <typename... Args>
struct Outer
{
using Cb = std::function<void(Args...)>;
//using Inner = Args; // this one complains &quot;parameter pack must be expanded in this context&quot;
//using Inner = Args...; // this one complains &quot;parameter pack cannot be expanded in this context&quot;
template&lt;typename CbIn&gt;
void store(CbIn&amp;&amp; cb)
{
mCb = std::forward&lt;CbIn&gt;(cb);
}
void call(Args... args) noexcept
{
mCb(args...);
}
// cb here accepts Args... and returns OtherOuter* (specialized with different Args)
template&lt;typename OtherOuter, typename CbIn&gt;
void foo(CbIn&amp;&amp; cb)
{
store([cb{ std::forward&lt;CbIn&gt;(cb)}](Args... args)
{
OtherOuter * other = cb(std::forward&lt;Args&gt;(args)...);
other-&gt;store([](/*OtherOuter::Inner*/ ... otherArgs) // what to put here if not OtherOuter::Inner ???
{
// do something with otherArgs
([&amp;]
{
std::cout &lt;&lt; &quot;second &quot; &lt;&lt; otherArgs &lt;&lt; std::endl;
} (), ...);
});
std::cout &lt;&lt; &quot;first &quot; &lt;&lt; other-&gt;mFlag &lt;&lt; std::endl;
});
}
Cb mCb;
bool mFlag = false;
`};
`
And using the example:
using OuterIntBool = Outer<int, bool>;
using OuterString = Outer<std::string>;
// works
{
OuterIntBool outerIntBool;
outerIntBool.store([](int i, bool b)
{
bool isValid = i > 0 && b;
assert(isValid);
});
outerIntBool.call(1, true);
}
// does not work
{
OuterIntBool outerIntBool;
OuterString otherString;
outerIntBool.foo<OuterString>([&otherString](int/* i*/, bool b)
{
otherString.mFlag = b;
return &otherString;
});
outerIntBool.call(1, true);
otherString.call("bar");
}
答案1 {#1}
得分: 3
如何"保存"可变模板参数?
直接的方式,就像我们对待常规模板参数一样,不可行:
template <typename T>
struct s1
{
using type = T; // OK
};
template <typename... Ts>
struct s2
{
using types = Ts...; // KO
};
但是,有几种方法可以"保存"可变参数:
-
使用某种可变类型来保存它,例如
std::tuple
或您自己的type_list
template <typename... Ts> struct type_list {}; template <typename... Args> struct Outer { using type1s = std::tuple<Args...>; using type2s = type_list<Args...>; };
-
一种解构的方式:
template <typename... Args> struct Outer { constexpr type_size = sizeof...(Args); template <std::size_t I> using type = std::tuple_element_t<I, std::tuple<Args...>>; };
-
使用外部类型特性(与上述任何结果都可以)。例如解构的方式示例:
template <typename T> constexpr std::size_t outer_size; template <typename... Ts> constexpr std::size_t outer_size<Outer<Ts...>> = sizeof...(Ts); template <std::size_t I, typename T> struct arg_element; template <std::size_t I, typename... Ts> struct arg_element<I, Outer<Ts...>> { using type = std::tuple_element_t<I, std::tuple<Ts...>>; };
如何使用保存的参数?
std::index_sequence
可能会有所帮助:
template <typename Outer>
void get_printer(const Outer&)
{
[]<std::size_t... Is>(std::index_sequence<Is...>){
// typename Outer::template type<Is>... is not equivalent to the Ts...
// std::tuple_element_t<Is, typename Outer::types>... for tuple case
return [](const typename Outer::template type<Is>&... args){
(std::cout << args), ...);
};
}(std::make_index_sequence<Outer::size_type>()); // tuple_size<Outer::types>;
}
英文:
> How to "save" variadic template parameter?
The direct way, as we do with regular template argument is not possible:
template <typename T>
struct s1
{
using type = T; // OK
};
`template <typename... Ts>
struct s2
{
using types = Ts...; // KO
};
`
but, they are several ways to "save" variadic arguments:
-
Use some variadic type to keep it
std::tuple
or your own type_listtemplate <typename... Ts> struct type_list {}; `template <typename... Args> struct Outer { using type1s = std::tuple<Args...>; using type2s = type_list<Args...>; }; `
-
A destructured way:
template <typename... Args> struct Outer { constexpr type_size = sizeof...(Args); template <std::size_t I> using type = std::tuple_element_t<I, std::tuple<Args...>>; };
-
an external type_trait (with any of the above result). so destructured way example:
template <typename T> constexpr std::size_t outer_size; template \<typename... Ts\> constexpr std::size_t outer_size\<Outer\<Ts...\>\> = sizeof...(Ts); template \<std::size_t I, typename T\> struct arg_element; `template <std::size_t I, typename... Ts> struct arg_element<I, Outer<Ts...>> { using type = std::tuple_element_t<Is, std::tuple<Ts...>>; }; `
> How to use saved parameters?
std::index_sequence
might help:
template <typename Outer>
void get_printer(const Outer&)
{
[]<std::size_t... Is>(std::index_sequence<Is...>){
// typename Outer::template type<Is>... is not equivalent to the Ts...
// std::tuple_element_t<Is, typename Outer::types>... for tuple case
return [](const typename Outer::template type&lt;Is&gt;&amp;... args){
(std::cout &lt;&lt; args), ...);
};
}(std::make_index_sequence&lt;Outer::size_type&gt;()); // tuple_size&lt;Outer::types&gt;
`}
`
答案2 {#2}
得分: 1
你尝试过 other->store([](auto&& ... otherArgs)
吗?
英文:
Did you try other->store([](auto&& ... otherArgs)
?