I thought I'd benchmark some sorting algorithms, but I must be doing templates incorrectly:
#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/
If I "untemplate" dummysort
, it works.
void dummysort(std::vector<int>::iterator begin_it, std::vector<int>::iterator end_it)
Is there a way I can make it work generically, or, if not, can someone please give me a good explanation or thought experiment similar to this answer ?
As explained by nwp, a template function isn't a function; is only a recipe to construct a function.
So you can't pass the recipe
benchmark("bubblesort", dummysort, first);
but you can do it explicating the type an constructing a function from the recipe; I mean
benchmark("bubblesort", dummysort<std::vector<int>::iterator>, first);
Works but I find it a little ugly.
I propose you another solution: instead of a template function, construct a class (or struct) with a template operator()
in it; something like
struct dummySort
{
template <typename FI>
void operator() (FI begin_it, FI end_it)
{
++begin_it;
++end_it;
}
};
In this way you can call benchmark()
passing an object of this type without explicating the type of the iterator: it's the call to operator inside of benchmark()
that select the right type and construct the right operator.
The following is a full compiling example
#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);
}
There are a few ways around this problem.
You could explicitly specify the template arguments for dummysort
or you could static_cast
it to the appropriate function pointer type. This, to me, is a pretty bad solution as it locks into a specific overload choice rather than relying on the language's overload rules. If you change other arguments or types, you could get (best-case) broken code or (worst-case) code that works but does something unexpected
You could lift your function template to be a function object that has a call operator template:
struct dummysort_t { template <typename ForwardIterator> void operator()(ForwardIterator begin_it, ForwardIterator end_it) const { /* ... */ } } constexpr dummysort{}; // in C++17, also mark inline
This allows you to just directly pass in dummysort
to your function templates everywhere with the same expected behavior. The downsides to this solution are if you don't own dummysort
, this is of course a non-starter, and if you use dummysort
as a customization point, you need to do quite a bit more work to get this to have the expected behavior.
You could wrap it in a lambda. Since we're copying iterators, the simple form just works:
[](auto b, auto e) { dummysort(b, e); }
This delays template instantiation for dummysort
until the other function template actually invokes it. This gives you the exact behavior you want, and will work in all situations (and fixes the issues with #2). More generally, this pattern should be used like:
#define OVERLOADS_OF(name) [&](auto&&... args) -> decltype(name(std::forward<decltype(args)>(args)...)) { return name(std::forward<decltype(args)>(args)...); }
with which you would pass:
OVERLOADS_OF(dummysort)
which for this particular case is overkill, but is more generally useful.
Two minor comments: You should pass the v
parameter to benchmark
by reference std::vector<int> &v
and not by value std::vector<int> v
, since vectors are expensive objects to construct. And std::endl
is unnecessary with the standard streams; instead of
std::cout << "Value of the variable:" << std::endl;
std::cout << variable << std::endl;
you can just write
std::cout << "Value of the variable:\n";
std::cout << variable << '\n';
Which is both a lot less verbose and faster (not that it matters for the trivial case here).
To answer your question, no, the compiler would not be able to infer dummysort
's template parameter in this case, so you would have to write it dummysort<std::vector<int>::iterator>
. But there are ways you can simplify it:
1) If your compiler supports C++14 variable templates you can declare
template<typename T>
constexpr auto vecdummysort = dummysort<typename std::vector<T>::iterator>;
And then use it like
benchmark("bubblesort", vecdummysort<int>, first);
2a) With C++11 you can create an alias template
template<typename T>
using veciter = typename std::vector<T>::iterator;
Which lets you use dummysort
like
benchmark("bubblesort", dummysort<veciter<int>>, first);
2b) For the same vecdummysort
function as in example #1:
template<typename T>
void vecdummysort(veciter<T> first, veciter<T> last) {dummysort(first, last);}
3) You could also change the call to sort_func
in benchmark
to use raw pointers instead of iterators, so
sort_func(v.data(), v.data() + v.size());
Instead of
sort_func(v.begin(), v.end());
Which means you would be able to use benchmark
like
benchmark("bubblesort", dummysort<int*>, first);
4) You can change the interface of dummysort
to take the collection itself as the argument instead of iterators, which sacrifices a tiny bit of flexibility (eg if you wanted to sort only part of the collection, or use reverse iterators, and so forth) for a less verbose and more ergonomic interface
template<typename Collection>
void dummysort(Collection &c)
{
auto first = std::begin(c);
auto last = std::end(c);
}
Usage:
// benchmark()
sort_func(v);
// main()
benchmark("bubblesort", dummysort<std::vector<int>>, first);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.