2016-09-24 101 views
2

我正在处理一个问题,将T类型的NxN矩阵旋转90度。在干燥的精神,我想我的旋转功能的函数签名看起来像这样:如何使用模板推断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); 

这将允许我简单地通过传递不同的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推断模板类型?

+0

任何理由,你为什么不能简单地用一个模板类型来表示的功能? – WhiZTiM

+0

@WhiZTiM如果我明白你的意思,你建议一个全局作用域的'std :: function '。它是否正确?如果是这样,这绝对是一种选择,但我不确定这是多么地道/恰当。我对''很不熟悉,并且还不知道这些技巧。 :) – erip

+0

@erip - 不确定要进行...什么是错误的'std :: function swap_clockwise(four_way_swap_clockwise );'? – max66

回答

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

live example

另一种方法是来包装你超载设置为lambda:

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

然后通过overloads你的函数。该lambda代表全部一次超过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拉姆达本身:

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解决您的问题。

总之,解决问题的方法很多。

+0

这里有很多值得思考的地方。很好的答案。 :) – erip

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

啊,是的。我绝对忽略了模板需要实例化。 – erip

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] 
      ); 
     } 
    } 
} 
+0

这个更通用的版本会有什么用法? – erip

+1

@erip:完全相同的用法,除了允许使用适当的签名进行_any_任意调用,而不是强制使用'std :: function <>'以及其固有的低效率。 – ildjarn

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

初始化'swap_clockwise'时不需要提供模板类型吗? – erip

+0

不需要。您需要结合其他海报提到的关于使rotate_90更具通用性的内容。 – lrm29

相关问题