单个函数中的多个参数包?
Multiple parameter packs in a single function?
我正在尝试创建一个接受两个对象参数包的函数。有两个模板化基础 类,我想将派生 类 的实例传递给此函数。考虑这个例子。
template <int N>
struct First {};
template <int N>
struct Second {};
// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};
template <int... firstInts, int... secondInts>
void function(float f, First<firstInts> &... first, Second<secondInts> &... second) {
// ...
}
我想做的是这样调用 function
FirstImpl firstImpl;
OtherFirstImpl otherFirstImpl;
SecondImpl secondImpl;
OtherSecondImpl otherSecondImpl;
function(9.5f, firstImpl, otherFirstImpl, secondImpl, otherSecondImpl);
但是这个例子不会编译。编译器似乎试图将所有内容打包到第二个参数包中但失败了,因为 FirstImpl
无法隐式转换 Second<N>
.
我该如何解决这个问题?
为什么不直接将 class 本身作为模板参数传递?像这样:
template <int N>
struct First {};
template <int N>
struct Second {};
// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};
template <typename FirstSpec, typename SecondSpec>
void function(float f, FirstSpec & first, SecondSpec & second) {
// ...
}
不完全是你问的,但是......你可以使用可变模板模板 int
容器(Cnt
,在下面的例子中)统一两个列表,然后检测每个参数,如果是 First
或 Second
(参见 std::is_same_v
的用法)
以下是完整的工作示例
#include <string>
#include <vector>
#include <iostream>
#include <type_traits>
template <int>
struct First {};
template <int>
struct Second {};
// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};
template <template <int> class ... Cnt, int... Ints>
void function (float f, Cnt<Ints> & ... args)
{
(std::cout << ... << std::is_same_v<Cnt<Ints>, First<Ints>>);
}
int main()
{
FirstImpl firstImpl;
FirstImpl otherFirstImpl;
SecondImpl secondImpl;
SecondImpl otherSecondImpl;
function(9.5f, firstImpl, otherFirstImpl, secondImpl, otherSecondImpl);
}
用两个可变参数包定义一些东西几乎是不可能的。一旦遇到可变参数包,它喜欢消耗所有剩余的参数,不留任何碎屑供第二个参数包使用。
然而,正如我所提到的,在许多情况下您可以使用元组,并且在 C++17 中使用推导指南,调用约定仅比其他情况稍长。
使用 gcc 7.3.1 测试,-std=c++17
模式:
#include <tuple>
template <int N>
struct First {};
template <int N>
struct Second {};
template <int... firstInts, int... secondInts>
void function(std::tuple<First<firstInts>...> a,
std::tuple<Second<secondInts>...> b)
{
}
int main(int, char* [])
{
function( std::tuple{ First<4>{}, First<3>{} },
std::tuple{ Second<1>{}, Second<4>{} });
}
这是基本思想。在你的情况下,你有 subclasses 来处理,所以一个更复杂的方法是必要的,可能两个元组的初始声明只是一个通用的 std::tuple< First...>
和 std::tuple<Second...>
,加上一些额外的模板。可能需要 First
和 Second
在 class 成员声明中声明它们自己的类型,然后让前面提到的 template-fu 查找 class 成员,并找出它正在处理哪个 superclass。
但以上是如何从单个可变参数列表中指定两组参数,然后进一步使用它的基本思想...
让我们首先编写一个变量模板,它确定类型是否派生自 First
:
template <int N>
constexpr std::true_type is_first(First<N> const &) { return {}; }
template <int N>
constexpr std::false_type is_first(Second<N> const &) { return {}; }
template <class T>
constexpr bool is_first_v = decltype( is_first(std::declval<T>()) )::value;
还有一个结构Split
,它收集First
和Second
类型的索引:
template <class, class, class, std::size_t I = 0> struct Split;
template <
std::size_t... FirstInts,
std::size_t... SecondInts,
std::size_t N
>
struct Split<
std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts...>,
std::tuple<>,
N
> {
using firsts = std::index_sequence<FirstInts...>;
using seconds = std::index_sequence<SecondInts...>;
};
template <
std::size_t... FirstInts,
std::size_t... SecondInts,
std::size_t I,
typename T,
typename... Tail
>
struct Split<
std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts...>,
std::tuple<T, Tail...>,
I
> : std::conditional_t<
is_first_v<T>,
Split<std::index_sequence<FirstInts..., I>,
std::index_sequence<SecondInts...>,
std::tuple<Tail...>,
I + 1
>,
Split<std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts..., I>,
std::tuple<Tail...>,
I + 1
>
> {};
就像我在评论中告诉你的那样,将成员 value
添加到 First
和 Second
(或从 std:integral_constant
继承),这使我们可以编写以下:
template <std::size_t... FirstIdx, std::size_t... SecondIdx, typename Tuple>
void function_impl(float f, std::index_sequence<FirstIdx...>, std::index_sequence<SecondIdx...>, Tuple const & tup) {
((std::cout << "firstInts: ") << ... << std::get<FirstIdx>(tup).value) << '\n';
((std::cout << "secondInts: ") << ... << std::get<SecondIdx>(tup).value) << '\n';
// your implementation
}
template <class... Args>
void function(float f, Args&&... args) {
using split = Split<std::index_sequence<>,std::index_sequence<>, std::tuple<std::decay_t<Args>...>>;
function_impl(f, typename split::firsts{}, typename split::seconds{}, std::forward_as_tuple(args...));
}
我正在尝试创建一个接受两个对象参数包的函数。有两个模板化基础 类,我想将派生 类 的实例传递给此函数。考虑这个例子。
template <int N>
struct First {};
template <int N>
struct Second {};
// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};
template <int... firstInts, int... secondInts>
void function(float f, First<firstInts> &... first, Second<secondInts> &... second) {
// ...
}
我想做的是这样调用 function
FirstImpl firstImpl;
OtherFirstImpl otherFirstImpl;
SecondImpl secondImpl;
OtherSecondImpl otherSecondImpl;
function(9.5f, firstImpl, otherFirstImpl, secondImpl, otherSecondImpl);
但是这个例子不会编译。编译器似乎试图将所有内容打包到第二个参数包中但失败了,因为 FirstImpl
无法隐式转换 Second<N>
.
我该如何解决这个问题?
为什么不直接将 class 本身作为模板参数传递?像这样:
template <int N>
struct First {};
template <int N>
struct Second {};
// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};
template <typename FirstSpec, typename SecondSpec>
void function(float f, FirstSpec & first, SecondSpec & second) {
// ...
}
不完全是你问的,但是......你可以使用可变模板模板 int
容器(Cnt
,在下面的例子中)统一两个列表,然后检测每个参数,如果是 First
或 Second
(参见 std::is_same_v
的用法)
以下是完整的工作示例
#include <string>
#include <vector>
#include <iostream>
#include <type_traits>
template <int>
struct First {};
template <int>
struct Second {};
// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};
template <template <int> class ... Cnt, int... Ints>
void function (float f, Cnt<Ints> & ... args)
{
(std::cout << ... << std::is_same_v<Cnt<Ints>, First<Ints>>);
}
int main()
{
FirstImpl firstImpl;
FirstImpl otherFirstImpl;
SecondImpl secondImpl;
SecondImpl otherSecondImpl;
function(9.5f, firstImpl, otherFirstImpl, secondImpl, otherSecondImpl);
}
用两个可变参数包定义一些东西几乎是不可能的。一旦遇到可变参数包,它喜欢消耗所有剩余的参数,不留任何碎屑供第二个参数包使用。
然而,正如我所提到的,在许多情况下您可以使用元组,并且在 C++17 中使用推导指南,调用约定仅比其他情况稍长。
使用 gcc 7.3.1 测试,-std=c++17
模式:
#include <tuple>
template <int N>
struct First {};
template <int N>
struct Second {};
template <int... firstInts, int... secondInts>
void function(std::tuple<First<firstInts>...> a,
std::tuple<Second<secondInts>...> b)
{
}
int main(int, char* [])
{
function( std::tuple{ First<4>{}, First<3>{} },
std::tuple{ Second<1>{}, Second<4>{} });
}
这是基本思想。在你的情况下,你有 subclasses 来处理,所以一个更复杂的方法是必要的,可能两个元组的初始声明只是一个通用的 std::tuple< First...>
和 std::tuple<Second...>
,加上一些额外的模板。可能需要 First
和 Second
在 class 成员声明中声明它们自己的类型,然后让前面提到的 template-fu 查找 class 成员,并找出它正在处理哪个 superclass。
但以上是如何从单个可变参数列表中指定两组参数,然后进一步使用它的基本思想...
让我们首先编写一个变量模板,它确定类型是否派生自 First
:
template <int N>
constexpr std::true_type is_first(First<N> const &) { return {}; }
template <int N>
constexpr std::false_type is_first(Second<N> const &) { return {}; }
template <class T>
constexpr bool is_first_v = decltype( is_first(std::declval<T>()) )::value;
还有一个结构Split
,它收集First
和Second
类型的索引:
template <class, class, class, std::size_t I = 0> struct Split;
template <
std::size_t... FirstInts,
std::size_t... SecondInts,
std::size_t N
>
struct Split<
std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts...>,
std::tuple<>,
N
> {
using firsts = std::index_sequence<FirstInts...>;
using seconds = std::index_sequence<SecondInts...>;
};
template <
std::size_t... FirstInts,
std::size_t... SecondInts,
std::size_t I,
typename T,
typename... Tail
>
struct Split<
std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts...>,
std::tuple<T, Tail...>,
I
> : std::conditional_t<
is_first_v<T>,
Split<std::index_sequence<FirstInts..., I>,
std::index_sequence<SecondInts...>,
std::tuple<Tail...>,
I + 1
>,
Split<std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts..., I>,
std::tuple<Tail...>,
I + 1
>
> {};
就像我在评论中告诉你的那样,将成员 value
添加到 First
和 Second
(或从 std:integral_constant
继承),这使我们可以编写以下:
template <std::size_t... FirstIdx, std::size_t... SecondIdx, typename Tuple>
void function_impl(float f, std::index_sequence<FirstIdx...>, std::index_sequence<SecondIdx...>, Tuple const & tup) {
((std::cout << "firstInts: ") << ... << std::get<FirstIdx>(tup).value) << '\n';
((std::cout << "secondInts: ") << ... << std::get<SecondIdx>(tup).value) << '\n';
// your implementation
}
template <class... Args>
void function(float f, Args&&... args) {
using split = Split<std::index_sequence<>,std::index_sequence<>, std::tuple<std::decay_t<Args>...>>;
function_impl(f, typename split::firsts{}, typename split::seconds{}, std::forward_as_tuple(args...));
}