模板化函数可以作为另一个函数的模板参数吗?

时间:2017-11-09 16:50:56

标签: c++ templates c++14 template-meta-programming

我以为我会对一些排序算法进行基准测试,但我必须正确地做模板:

代码

#include <iostream>
#include <vector>

template <typename ForwardIterator>
void dummysort(ForwardIterator begin_it, ForwardIterator end_it)
{
    // pretend to use these and sort stuff
    ++begin_it;
    ++end_it;
}

template<typename SortFunc>
void benchmark(const char* name, SortFunc sort_func, std::vector<int> v)
{
    std::cout << name << std::endl;
    sort_func(v.begin(), v.end());
}

int main()
{
    std::vector<int> first = {3, 2, 1};

    benchmark("bubblesort", dummysort, first);
}

错误

10:48 $ clang -std=c++14 tmp.cpp
tmp.cpp:30:5: error: no matching function for call to 'benchmark'
    benchmark("bubblesort", dummysort, first);
    ^~~~~~~~~
tmp.cpp:20:6: note: candidate template ignored: couldn't infer template argument 'SortFunc'
void benchmark(const char* name, SortFunc sort_func, std::vector<int> v)
     ^
1 error generated.

编译器信息

10:47 $ clang --version
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.0.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/

如果我&#34; untemplate&#34; dummysort,它有效。

void dummysort(std::vector<int>::iterator begin_it, std::vector<int>::iterator end_it)

问题

有没有办法可以让它一般地运作,或者,如果没有,有人可以给我一个类似于this answer的好解释或思考实验吗?

3 个答案:

答案 0 :(得分:4)

正如nwp所解释的,模板函数不是函数;只是构建函数的一个方法。

所以你不能传递食谱

benchmark("bubblesort", dummysort, first);

但是你可以用它来解释从配方中构造函数的类型;我的意思是

benchmark("bubblesort", dummysort<std::vector<int>::iterator>, first);

有效,但我发现它有点难看。

我建议你另一个解决方案:使用模板operator()构建一个类(或结构),而不是模板函数;

之类的东西
struct dummySort
 {
   template <typename FI>
   void operator() (FI begin_it, FI end_it)
    {
      ++begin_it;
      ++end_it; 
    }
 };

通过这种方式,您可以调用benchmark()传递此类型的对象而不解释迭代器的类型:它是benchmark()内部对运算符的调用,它选择正确的类型并构造正确的运算符

以下是完整的编译示例

#include <iostream>
#include <vector>

struct dummySort
 {
   template <typename FI>
   void operator() (FI begin_it, FI end_it)
    { ++begin_it; ++end_it; }
 };

template <typename SortFunc>
void benchmark (char const * name, SortFunc sort_func, std::vector<int> v)
 {
   std::cout << name << std::endl;
   sort_func(v.begin(), v.end());
 }

int main()
 {
   std::vector<int> first = {3, 2, 1};

   benchmark("bubblesort", dummySort{}, first);
 }

答案 1 :(得分:1)

解决此问题有几种方法。

  1. 您可以显式指定static_cast的模板参数,也可以struct dummysort_t { template <typename ForwardIterator> void operator()(ForwardIterator begin_it, ForwardIterator end_it) const { /* ... */ } } constexpr dummysort{}; // in C++17, also mark inline 指定相应的函数指针类型。对我来说,这是一个非常糟糕的解决方案,因为它锁定了特定的重载选择而不是依赖于语言的重载规则。如果您更改其他参数或类型,您可以获得(最佳情况)损坏代码或(最坏情况)代码,但可以执行某些操作但

  2. 您可以将功能模板提升为具有呼叫操作员模板的对象功能:

    dummysort

    这使您可以直接将dummysort传递到具有相同预期行为的所有功能模板。这个解决方案的缺点是,如果你不拥有dummysort,这当然是一个非首发,如果你使用[](auto b, auto e) { dummysort(b, e); } 作为自定义点,你需要做更多的工作让它具有预期的行为。

  3. 你可以用lambda包装它。因为我们正在复制迭代器,所以简单的形式就可以了:

    dummysort

    这会延迟#define OVERLOADS_OF(name) [&](auto&&... args) -> decltype(name(std::forward<decltype(args)>(args)...)) { return name(std::forward<decltype(args)>(args)...); } 的模板实例化,直到另一个函数模板实际调用它为止。这为您提供了您想要的确切行为,并且可以在所有情况下工作(并修复了#2的问题)。更一般地说,这种模式应该像:

    OVERLOADS_OF(dummysort)
    

    你将通过:

    git rebase --onto <commitId>
    

    对于这种特殊情况来说是过度杀伤,但更普遍有用。

答案 2 :(得分:0)

两条小评论:您应该通过引用v而不是benchmarkstd::vector<int> &v参数传递给std::vector<int> v,因为向量是构建的昂贵对象。标准流不需要std::endl;而不是

std::cout << "Value of the variable:" << std::endl;
std::cout << variable << std::endl;

你可以写

std::cout << "Value of the variable:\n";
std::cout << variable << '\n';

这两者都不那么冗长和快速(不是因为这里的琐碎案例很重要)。

要回答你的问题,不,编译器在这种情况下无法推断dummysort的模板参数,因此你必须将其编写为dummysort<std::vector<int>::iterator>。但有一些方法可以简化它:

1)如果您的编译器支持C ++ 14 variable templates,您可以声明

template<typename T>
constexpr auto vecdummysort = dummysort<typename std::vector<T>::iterator>;

然后像

一样使用它
benchmark("bubblesort", vecdummysort<int>, first);

2a)使用C ++ 11,您可以创建alias template

template<typename T>
using veciter = typename std::vector<T>::iterator;

允许您使用dummysort之类的

benchmark("bubblesort", dummysort<veciter<int>>, first);

2b)对于与示例#1中相同的vecdummysort函数:

template<typename T>
void vecdummysort(veciter<T> first, veciter<T> last) {dummysort(first, last);}

3)您也可以将sort_func中的benchmark调用更改为使用原始指针而不是迭代器,所以

sort_func(v.data(), v.data() + v.size());

而不是

sort_func(v.begin(), v.end());

这意味着您可以使用benchmark之类的

benchmark("bubblesort", dummysort<int*>, first);

4)您可以更改dummysort的接口以将集合本身​​作为参数而不是迭代器,这会牺牲一点点灵活性(例如,如果您只想对集合的一部分进行排序,或者使用反向迭代器等等,以获得更简洁,更符合人体工程学的界面

template<typename Collection>
void dummysort(Collection &c)
{
    auto first = std::begin(c);
    auto last = std::end(c);
}

用法:

// benchmark()
sort_func(v);
// main()
benchmark("bubblesort", dummysort<std::vector<int>>, first);
相关问题