如何使用模板推导出std :: function的参数类型?

时间:2016-09-24 20:59:10

标签: c++ templates c++11 c++14 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

4 个答案:

答案 0 :(得分:4)

您可能希望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]
            );
        }
    }
}

答案 1 :(得分:4)

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;
}; 

http://philosophy.eserver.org/texts.htm

另一种方法是将重载集包装成lambda:

auto overloads = [](auto&&...args){ return four_way_swap_clockwise(decltype(args)(args)...); };

然后将overloads传递给您的函数。这个lambda一次代表four_way_swap_clockwise重载的 all

我们也可以通过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完全免费,让算法中出现任何失败。这也略微提升了性能。您仍然需要使用four_way_swap_clockwise或lambda-wrapper技术消除<int>的歧义。

另一种方法是让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解决了您的问题。

简而言之,您的问题有很多方法。

答案 2 :(得分:2)

要调用该函数,

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);

答案 3 :(得分:2)

您可以将模板功能变为仿函数:

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);