如何使用模板推导出 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;
}; 

live example.

另一种方法是将重载集包装到 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);