如何使用模板推导出 std::function 的参数类型?
How can I use templates to deduce the parameter types of a std::function?
我正在研究将类型 T
的 NxN 矩阵旋转 90 度的问题。本着 DRY 的精神,我希望我的旋转函数的函数签名看起来像这样:
template <typename T, std::size_t N>
void rotate_90(Matrix<T, N>& m, std::function<void(T&, T&, T&, T&)> swap_direction);
这将允许我通过传递不同的 std::function<void(T&, T&, T&, T&)>
.
来使用相同的函数顺时针和逆时针交换
我目前有以下代码:
#include <iostream>
#include <array>
#include <functional>
template <typename T, std::size_t N>
using Matrix = std::array<std::array<T, N>, N>;
template <typename T>
void four_way_swap_clockwise(T& top_left, T& top_right, T& bottom_left, T& bottom_right) {
T temp = top_left;
top_left = top_right;
top_right = bottom_right;
bottom_right = bottom_left;
bottom_left = temp;
}
template <typename T, std::size_t N>
void rotate_90(Matrix<T, N>& m, std::function<void(T&, T&, T&, T&)> swap_direction) {
for(std::size_t i = 0; i < N/2; ++i) {
for(std::size_t j = 0; j < (N+1)/2; ++j) {
swap_direction(
m[i][j],
m[N-j-1][i],
m[j][N-i-1],
m[N-i-1][N-j-1]
);
}
}
}
int main() {
constexpr std::size_t N = 5;
Matrix<int, N> m {{
{{1,2,3,4,5}},
{{6,7,8,9,10}},
{{11,12,13,14,15}},
{{16,17,18,19,20}},
{{21,22,23,24,25}}
}};
std::function<void(int&, int&, int&, int&)> swap_clockwise(four_way_swap_clockwise);
rotate_90(m, swap_clockwise);
}
当前无法编译,失败并出现以下错误:
error: no matching function for call to 'std::function<void(int&, int&, int&, int&)>::function(<unresolved overloaded function type>)'
std::function<void(int&, int&, int&, int&)> swap_clockwise(four_way_swap_clockwise);
然而,即使编译通过,也违背了模板编程指定交换函数参数类型的目的(即在std::function<void(int&, int&, int&, int&)> swap_clockwise(four_way_swap_clockwise);
的定义中)。
如何通过推导的模板类型传递 std::function
?
要调用函数,
template <typename T, std::size_t N>
void rotate_90(Matrix<T, N>& m, std::function<void(T&, T&, T&, T&)> swap_direction);
给定:
template <typename T>
void four_way_swap_clockwise(T& top_left, T& top_right, T& bottom_left, T& bottom_right);
你可以试试这个:
rotate_90<int>(m, four_way_swap_clockwise<int>);
至于为什么不能这样称呼:
rotate_90(m, four_way_swap_clockwise);
部分原因是名称 four_way_swap_clockwise
是一个 模板函数 而 不是 一个 函数,使用这样的名字需要实例化。我将其实例化为 four_way_swap_clockwise<int>
更好的是,根据我对你的问题的第一条评论,最好把 rotate_90
写成这样:
template <typename T, std::size_t N, typename Func>
void rotate_90(Matrix<T, N>& m, Func swap_direction);
您可能希望 rotate_90
更通用,如下所示:
template <typename T, std::size_t N, typename F>
void rotate_90(Matrix<T, N>& m, F swap_direction) {
for(std::size_t i = 0; i < N/2; ++i) {
for(std::size_t j = 0; j < (N+1)/2; ++j) {
swap_direction(
m[i][j],
m[N-j-1][i],
m[j][N-i-1],
m[N-i-1][N-j-1]
);
}
}
}
template<class T> struct tag_t{using type=T;};
template<class T> using block_deduction=typename tag_t<T>::type;
此构造阻止 C++ 尝试从函数参数推导出模板参数。
template <typename T, std::size_t N>
void rotate_90(Matrix<T, N>& m, block_deduction<std::function<void(T&, T&, T&, T&)>> swap_direction) {
现在第二个参数的类型总是从第一个参数的类型推导出来的!
下一个问题是 std::function
没有消除重载函数名称的歧义。重载函数名称不是 C++ 值,它是一组名称(在正确的上下文中)找到一个值。 std::function
构造 不是 这些上下文之一。
我们可以用这样的附加构造函数扩展 std::function
:
template<class Sig, class F=std::function<Sig>>
struct my_func:F {
using F::F;
using F::operator=;
my_func( Sig* ptr ):F(ptr) {}
my_func& operator=( Sig* ptr ) {
F::operator=(ptr);
return *this;
}
my_func()=default;
my_func(my_func&&)=default;
my_func(my_func const&)=default;
my_func& operator=(my_func&&)=default;
my_func& operator=(my_func const&)=default;
};
另一种方法是将重载集包装到 lambda 中:
auto overloads = [](auto&&...args){ return four_way_swap_clockwise(decltype(args)(args)...); };
然后将 overloads
传递给您的函数。此 lambda 表示 all 一次 four_way_swap_clockwise
的重载。
我们也可以通过four_way_swap_clockwise<int>
.
手动消除歧义
这两个仍然需要上面的 block_deduction
技巧。
可以考虑的替代方案是:
template <typename T, std::size_t N, class F>
void rotate_90(Matrix<T, N>& m, F&& swap_direction)
我们让 swap_direction
完全自由,让算法中出现任何故障。这也带来了轻微的性能提升。您仍然需要使用 <int>
或 lambda-wrapper 技术来消除 four_way_swap_clockwise
的歧义。
另一种方法是使 for_way_swap_clockwise
成为 lambda 本身:
auto four_way_swap_clockwise = [](auto& top_left, auto& top_right, auto& bottom_left, auto& bottom_right) {
auto temp = top_left;
top_left = top_right;
top_right = bottom_right;
bottom_right = bottom_left;
bottom_left = temp;
};
现在它是一个带有模板 operator()
重载的对象。 block_deduction
可以解决您的问题。
简而言之,有很多方法可以解决您的问题。
你可以让你的模板函数成为仿函数:
struct four_way_swap_clockwise {
template <typename T>
void
operator()(T& top_left, T& top_right, T& bottom_left, T& bottom_right) {
T temp = top_left;
top_left = top_right;
top_right = bottom_right;
bottom_right = bottom_left;
bottom_left = temp;
}
};
然后调用:
four_way_swap_clockwise swap_clockwise;
rotate_90(m, swap_clockwise);
我正在研究将类型 T
的 NxN 矩阵旋转 90 度的问题。本着 DRY 的精神,我希望我的旋转函数的函数签名看起来像这样:
template <typename T, std::size_t N>
void rotate_90(Matrix<T, N>& m, std::function<void(T&, T&, T&, T&)> swap_direction);
这将允许我通过传递不同的 std::function<void(T&, T&, T&, T&)>
.
我目前有以下代码:
#include <iostream>
#include <array>
#include <functional>
template <typename T, std::size_t N>
using Matrix = std::array<std::array<T, N>, N>;
template <typename T>
void four_way_swap_clockwise(T& top_left, T& top_right, T& bottom_left, T& bottom_right) {
T temp = top_left;
top_left = top_right;
top_right = bottom_right;
bottom_right = bottom_left;
bottom_left = temp;
}
template <typename T, std::size_t N>
void rotate_90(Matrix<T, N>& m, std::function<void(T&, T&, T&, T&)> swap_direction) {
for(std::size_t i = 0; i < N/2; ++i) {
for(std::size_t j = 0; j < (N+1)/2; ++j) {
swap_direction(
m[i][j],
m[N-j-1][i],
m[j][N-i-1],
m[N-i-1][N-j-1]
);
}
}
}
int main() {
constexpr std::size_t N = 5;
Matrix<int, N> m {{
{{1,2,3,4,5}},
{{6,7,8,9,10}},
{{11,12,13,14,15}},
{{16,17,18,19,20}},
{{21,22,23,24,25}}
}};
std::function<void(int&, int&, int&, int&)> swap_clockwise(four_way_swap_clockwise);
rotate_90(m, swap_clockwise);
}
当前无法编译,失败并出现以下错误:
error: no matching function for call to 'std::function<void(int&, int&, int&, int&)>::function(<unresolved overloaded function type>)'
std::function<void(int&, int&, int&, int&)> swap_clockwise(four_way_swap_clockwise);
然而,即使编译通过,也违背了模板编程指定交换函数参数类型的目的(即在std::function<void(int&, int&, int&, int&)> swap_clockwise(four_way_swap_clockwise);
的定义中)。
如何通过推导的模板类型传递 std::function
?
要调用函数,
template <typename T, std::size_t N>
void rotate_90(Matrix<T, N>& m, std::function<void(T&, T&, T&, T&)> swap_direction);
给定:
template <typename T>
void four_way_swap_clockwise(T& top_left, T& top_right, T& bottom_left, T& bottom_right);
你可以试试这个:
rotate_90<int>(m, four_way_swap_clockwise<int>);
至于为什么不能这样称呼:
rotate_90(m, four_way_swap_clockwise);
部分原因是名称 four_way_swap_clockwise
是一个 模板函数 而 不是 一个 函数,使用这样的名字需要实例化。我将其实例化为 four_way_swap_clockwise<int>
更好的是,根据我对你的问题的第一条评论,最好把 rotate_90
写成这样:
template <typename T, std::size_t N, typename Func>
void rotate_90(Matrix<T, N>& m, Func swap_direction);
您可能希望 rotate_90
更通用,如下所示:
template <typename T, std::size_t N, typename F>
void rotate_90(Matrix<T, N>& m, F swap_direction) {
for(std::size_t i = 0; i < N/2; ++i) {
for(std::size_t j = 0; j < (N+1)/2; ++j) {
swap_direction(
m[i][j],
m[N-j-1][i],
m[j][N-i-1],
m[N-i-1][N-j-1]
);
}
}
}
template<class T> struct tag_t{using type=T;};
template<class T> using block_deduction=typename tag_t<T>::type;
此构造阻止 C++ 尝试从函数参数推导出模板参数。
template <typename T, std::size_t N>
void rotate_90(Matrix<T, N>& m, block_deduction<std::function<void(T&, T&, T&, T&)>> swap_direction) {
现在第二个参数的类型总是从第一个参数的类型推导出来的!
下一个问题是 std::function
没有消除重载函数名称的歧义。重载函数名称不是 C++ 值,它是一组名称(在正确的上下文中)找到一个值。 std::function
构造 不是 这些上下文之一。
我们可以用这样的附加构造函数扩展 std::function
:
template<class Sig, class F=std::function<Sig>>
struct my_func:F {
using F::F;
using F::operator=;
my_func( Sig* ptr ):F(ptr) {}
my_func& operator=( Sig* ptr ) {
F::operator=(ptr);
return *this;
}
my_func()=default;
my_func(my_func&&)=default;
my_func(my_func const&)=default;
my_func& operator=(my_func&&)=default;
my_func& operator=(my_func const&)=default;
};
另一种方法是将重载集包装到 lambda 中:
auto overloads = [](auto&&...args){ return four_way_swap_clockwise(decltype(args)(args)...); };
然后将 overloads
传递给您的函数。此 lambda 表示 all 一次 four_way_swap_clockwise
的重载。
我们也可以通过four_way_swap_clockwise<int>
.
这两个仍然需要上面的 block_deduction
技巧。
可以考虑的替代方案是:
template <typename T, std::size_t N, class F>
void rotate_90(Matrix<T, N>& m, F&& swap_direction)
我们让 swap_direction
完全自由,让算法中出现任何故障。这也带来了轻微的性能提升。您仍然需要使用 <int>
或 lambda-wrapper 技术来消除 four_way_swap_clockwise
的歧义。
另一种方法是使 for_way_swap_clockwise
成为 lambda 本身:
auto four_way_swap_clockwise = [](auto& top_left, auto& top_right, auto& bottom_left, auto& bottom_right) {
auto temp = top_left;
top_left = top_right;
top_right = bottom_right;
bottom_right = bottom_left;
bottom_left = temp;
};
现在它是一个带有模板 operator()
重载的对象。 block_deduction
可以解决您的问题。
简而言之,有很多方法可以解决您的问题。
你可以让你的模板函数成为仿函数:
struct four_way_swap_clockwise {
template <typename T>
void
operator()(T& top_left, T& top_right, T& bottom_left, T& bottom_right) {
T temp = top_left;
top_left = top_right;
top_right = bottom_right;
bottom_right = bottom_left;
bottom_left = temp;
}
};
然后调用:
four_way_swap_clockwise swap_clockwise;
rotate_90(m, swap_clockwise);