Может ли шаблонная функция быть аргументом шаблона для другой функции?
Я думал, что сравню некоторые алгоритмы сортировки, но я должен делать шаблоны неправильно:
Код
#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/
Если я "untemplate" dummysort
, оно работает.
void dummysort(std::vector<int>::iterator begin_it, std::vector<int>::iterator end_it)
Вопрос
Есть ли способ, которым я могу заставить его работать в общем, или, если нет, кто-то может дать мне хорошее объяснение или мысленный эксперимент, подобный этому ответу?
3 ответа
Два небольших комментария: Вы должны передать v
параметр для benchmark
по ссылке std::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, вы можете объявить
template<typename T>
constexpr auto vecdummysort = dummysort<typename std::vector<T>::iterator>;
А потом использовать как
benchmark("bubblesort", vecdummysort<int>, first);
2a) С C++11 вы можете создать шаблон псевдонима
template<typename T>
using veciter = typename std::vector<T>::iterator;
Который позволяет вам использовать dummysort
лайк
benchmark("bubblesort", dummysort<veciter<int>>, first);
2б) Для того же vecdummysort
функционировать как в примере № 1:
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);
Как объясняет nwp, функция шаблона не является функцией; это всего лишь рецепт для создания функции.
Таким образом, вы не можете передать рецепт
benchmark("bubblesort", dummysort, first);
но вы можете сделать это, объяснив тип a конструированием функции из рецепта; Я имею в виду
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);
}
Есть несколько способов обойти эту проблему.
Вы можете явно указать аргументы шаблона для
dummysort
или ты мог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
Это позволяет вам просто перейти непосредственно
dummysort
к вашим шаблонам функций везде с одинаковым ожидаемым поведением. Недостатки этого решения, если вы не владеетеdummysort
, это, конечно, не стартер, и если вы используетеdummysort
в качестве точки настройки вам нужно проделать еще немного работы, чтобы получить ожидаемое поведение.Вы могли бы обернуть это в лямбду. Поскольку мы копируем итераторы, простая форма просто работает:
[](auto b, auto e) { dummysort(b, e); }
Это задерживает создание шаблона для
dummysort
пока другой шаблон функции на самом деле не вызовет его. Это дает вам точное поведение, которое вы хотите, и будет работать во всех ситуациях (и устраняет проблемы с #2). В более общем смысле этот шаблон должен использоваться следующим образом:#define OVERLOADS_OF(name) [&](auto&&... args) -> decltype(name(std::forward<decltype(args)>(args)...)) { return name(std::forward<decltype(args)>(args)...); }
с которым вы бы проходили:
OVERLOADS_OF(dummysort)
что в данном конкретном случае является излишним, но в целом полезно.