如何编写对参数数组长度递归的 CPP 模板函数
How to write CPP template functions that is recursive on argument array length
假设我想编写一个函数 arrfill<N>
来填充长度为 N
的数组。下面是我试过的模板实现。
template<typename T>
bool arrfill(T arr[0], T v){;}
template<size_t N, typename T>
void arrfill(T arr[N], T v){
arr[0] = v;
arrfill<N-1>(arr+1, v);
}
int main(int argc, char const *argv[])
{
bool barr[4];
arrfill<4>(barr, true);
}
但是,这不会编译,因为模板实例化不会在 N
为 0
的情况下终止,并且会超过其最大深度。
编译器似乎不会将签名中的数组大小作为参数类型。我想知道指定它的正确方法是什么?
我通过定义一个新的 class 编码长度信息得出了一个解决方案。但是我不知道这是不是最优雅的方式
template<size_t N>
struct intClass{};
template<typename T>
bool arrfill(T arr[0], T v, intClass<0>){;}
template<size_t N, typename T>
void arrfill(T arr[N], T v, intClass<N>){
arr[0] = v;
arrfill(arr+1, v, intClass<N-1>());
}
template<size_t N, typename T>
void arrfill(T arr[N], T v){
arrfill(arr,v, intClass<N>());
}
你被参数衰减咬伤了。
参数衰减意味着 int arr[N]
是 int* arr
的花言巧语。 N
被完全忽略。
最重要的是,arrfill<N-1>
是对其第一个参数是 size_t(或兼容)的模板函数的调用。您的 T arr[0]
是一个 overload,它将类型作为其第一个模板参数。所以不能 selected.
要解决参数衰减问题,您应该通过引用获取数组。
template<typename T>
bool arrfill(T (&arr)[1], T v){arr[0]=v;}
template<size_t N, typename T>
void arrfill(T (&arr)[N], T v){
arr[0] = v;
arrfill(*reinterpret_cast<T(*)[N-1]>(arr+1), v);
}
遗憾的是,这是未定义的行为;我正在将一个数组的一部分转换为它不是的类型的数组。这恰好是我曾经使用过的每个 C++ 编译器都会消耗并做“正确的事情”的未定义行为,但它仍然是未定义的行为。我们应该避免这种情况,除非我们有充分的理由不这样做;虽然简洁明了,但干净的代码(在我看来)不是做 UB 的好理由。当编译器更新时,UB 可能会在 10 年后回来咬我们,我不想在每次编译器更新时都维护此代码并确保它仍然有效。
真的,使用打包和折叠。
template<size_t N, typename T,std::size_t...Is>
void arrfill(T (&arr)[N], T v,std::index_sequence<Is...>){
((void)(arr[Is]=v),...);
}
template<size_t N, typename T>
void arrfill(T (&arr)[N], T v){
arrfill(arr, v, std::make_index_sequence<N>{});
}
或者只使用 std::fill_n
.
template<size_t N, typename T>
void arrfill(T (&arr)[N], T v){
std::fill_n( std::begin(arr), N, v );
}
如果你真的,真的必须使用递归
template<size_t N, typename T>
void arrfill(T* arr, T v){
if constexpr(N==0) {
return;
} else {
arr[0] = v;
arrfill<N-1>(arr+1, v);
}
}
做到了。在 c++11 中我们不能使用 if constexpr。所以我们做点别的。
template<typename T>
void arrfill(std::integral_constant<std::size_t, 0>, T* arr, T const& v){
}
template<size_t N, typename T>
void arrfill(std::integral_constant<std::size_t, N>, T* arr, T const& v){
arr[0] = v;
arrfill(std::integral_constant<std::size_t, N-1>{}, arr+1, v);
}
template<size_t N, typename T>
void arrfill(T(&arr)[N], T const& v){
arrFill(std::integral_constant<std::size_t, N>{}, arr, v);
}
这让我们 select 使用重载的 0 案例。我们也自动推导出N
。
假设我想编写一个函数 arrfill<N>
来填充长度为 N
的数组。下面是我试过的模板实现。
template<typename T>
bool arrfill(T arr[0], T v){;}
template<size_t N, typename T>
void arrfill(T arr[N], T v){
arr[0] = v;
arrfill<N-1>(arr+1, v);
}
int main(int argc, char const *argv[])
{
bool barr[4];
arrfill<4>(barr, true);
}
但是,这不会编译,因为模板实例化不会在 N
为 0
的情况下终止,并且会超过其最大深度。
编译器似乎不会将签名中的数组大小作为参数类型。我想知道指定它的正确方法是什么?
我通过定义一个新的 class 编码长度信息得出了一个解决方案。但是我不知道这是不是最优雅的方式
template<size_t N>
struct intClass{};
template<typename T>
bool arrfill(T arr[0], T v, intClass<0>){;}
template<size_t N, typename T>
void arrfill(T arr[N], T v, intClass<N>){
arr[0] = v;
arrfill(arr+1, v, intClass<N-1>());
}
template<size_t N, typename T>
void arrfill(T arr[N], T v){
arrfill(arr,v, intClass<N>());
}
你被参数衰减咬伤了。
参数衰减意味着 int arr[N]
是 int* arr
的花言巧语。 N
被完全忽略。
最重要的是,arrfill<N-1>
是对其第一个参数是 size_t(或兼容)的模板函数的调用。您的 T arr[0]
是一个 overload,它将类型作为其第一个模板参数。所以不能 selected.
要解决参数衰减问题,您应该通过引用获取数组。
template<typename T>
bool arrfill(T (&arr)[1], T v){arr[0]=v;}
template<size_t N, typename T>
void arrfill(T (&arr)[N], T v){
arr[0] = v;
arrfill(*reinterpret_cast<T(*)[N-1]>(arr+1), v);
}
遗憾的是,这是未定义的行为;我正在将一个数组的一部分转换为它不是的类型的数组。这恰好是我曾经使用过的每个 C++ 编译器都会消耗并做“正确的事情”的未定义行为,但它仍然是未定义的行为。我们应该避免这种情况,除非我们有充分的理由不这样做;虽然简洁明了,但干净的代码(在我看来)不是做 UB 的好理由。当编译器更新时,UB 可能会在 10 年后回来咬我们,我不想在每次编译器更新时都维护此代码并确保它仍然有效。
真的,使用打包和折叠。
template<size_t N, typename T,std::size_t...Is>
void arrfill(T (&arr)[N], T v,std::index_sequence<Is...>){
((void)(arr[Is]=v),...);
}
template<size_t N, typename T>
void arrfill(T (&arr)[N], T v){
arrfill(arr, v, std::make_index_sequence<N>{});
}
或者只使用 std::fill_n
.
template<size_t N, typename T>
void arrfill(T (&arr)[N], T v){
std::fill_n( std::begin(arr), N, v );
}
如果你真的,真的必须使用递归
template<size_t N, typename T>
void arrfill(T* arr, T v){
if constexpr(N==0) {
return;
} else {
arr[0] = v;
arrfill<N-1>(arr+1, v);
}
}
做到了。在 c++11 中我们不能使用 if constexpr。所以我们做点别的。
template<typename T>
void arrfill(std::integral_constant<std::size_t, 0>, T* arr, T const& v){
}
template<size_t N, typename T>
void arrfill(std::integral_constant<std::size_t, N>, T* arr, T const& v){
arr[0] = v;
arrfill(std::integral_constant<std::size_t, N-1>{}, arr+1, v);
}
template<size_t N, typename T>
void arrfill(T(&arr)[N], T const& v){
arrFill(std::integral_constant<std::size_t, N>{}, arr, v);
}
这让我们 select 使用重载的 0 案例。我们也自动推导出N
。