Может ли шаблонная функция быть аргументом шаблона для другой функции?

Я думал, что сравню некоторые алгоритмы сортировки, но я должен делать шаблоны неправильно:

Код

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

Есть несколько способов обойти эту проблему.

  1. Вы можете явно указать аргументы шаблона для dummysort или ты мог static_cast это к соответствующему типу указателя функции. Для меня это довольно плохое решение, поскольку оно привязывается к конкретному выбору перегрузки, а не полагается на правила перегрузки языка. Если вы измените другие аргументы или типы, вы можете получить (в лучшем случае) неработающий код или (в худшем случае) код, который работает, но делает что-то неожиданное

  2. Вы можете поднять свой функциональный шаблон, чтобы он стал функциональным объектом с шаблоном оператора вызова:

    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 в качестве точки настройки вам нужно проделать еще немного работы, чтобы получить ожидаемое поведение.

  3. Вы могли бы обернуть это в лямбду. Поскольку мы копируем итераторы, простая форма просто работает:

    [](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)
    

    что в данном конкретном случае является излишним, но в целом полезно.

Другие вопросы по тегам