Перегрузка вложенной функции шаблона - PullRequest
2 голосов
/ 29 мая 2020

Я много думал, какой заголовок задать свой вопрос, но все равно не смог, поэтому, если вы найдете хороший, отредактируйте его.

Я пытаюсь написать функцию печати для vector или другой container<T> и есть еще одна функция печати для container<container<T>>, вот что я придумал:

template<typename T>
void print(T const& cont){
    for (const auto& i : cont) {
        cout << i << " ";
    }
    cout << endl;
}

template<typename T, template<typename> typename Cont>
void print(Cont<T> const& cont) {
    for (const auto& i : cont) {
        print(i);
    }
}

, и здесь у меня есть 2 моих целевых контейнера:

vector<vector<int>> subsets;
vector<int> subset;

Когда я вызываю print(subset);, программа работает, как ожидалось, но когда я вызываю print(subsets), компилятор начинает жаловаться:

error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const std::vector<int,std::allocator<int>>' (or there is no acceptable conversion)

Я пришел к выводу, что он все еще пытается вызвать функцию печати невложенного шаблона и не удается cout, поскольку я пытаюсь cout вектор.

Может ли кто-нибудь объяснить, почему разрешение перегрузки не работает так, как я ожидал, и что я сделал здесь не так? Даже когда я переименовал функцию nested-template в printn, она начала жаловаться по другой причине:

error C2784: 'void prints(const Cont<T> &)': could not deduce template argument for 'const Cont<T> &' from 'std::vector<std::vector<int,std::allocator<int>>,std::allocator<std::vector<int,std::allocator<int>>>>'

Ответы [ 3 ]

5 голосов
/ 29 мая 2020

Короткий, простой и недостаточный ответ: std::vector имеет 2 параметра шаблона. Вы также должны включить некоторый интервал:

template<class T, class A, template<class, class>class C>
void print(C<T,A> const& cont) {
  std::cout << "[ ";
  bool bFirst = true;
  for (const auto& i : cont) {
    if (!bFirst)
      std::cout << ", ";
    bFirst = false;
    print(i);
  }
  std::cout << " ]";
}

Чтобы перегрузка никогда не вызывалась.

Как только вы это сделаете, ваш код не будет работать, потому что у вас нет элемента - принтер. Поэтому замените свой другой l oop -printer на element-printer:

template<typename T>
void print(T const& i){
  std::cout << i;
}

Живой пример .

Тестовый код:

std::vector<int> a={1,2,3};
print(a);
std::cout << "\n";
std::vector<std::vector<int>> b = {a, a, a};
print(b);
std::cout << "\n";

Вывод:

[ 1, 2, 3 ]
[ [ 1, 2, 3 ], [ 1, 2, 3 ], [ 1, 2, 3 ] ]

этого недостаточно, потому что вам действительно нужно сделать что-то более интересное, чтобы определить, «является ли этот объект итерируемым» и «похож ли этот объект на кортеж», если вам нужен более серьезный принтер общего назначения . Обнаружение шаблонов Cont<A,B> - плохая замена.

Здесь - это код для определения возможности итерации чего-либо (игнорируйте неверно проверенный ответ, прочтите тот, на который я ссылался).

Затем выполните тест SFINAE на предмет «повторяется ли аргумент» в print для того, который выполняет for(:) l oop.

Следующее, что вам нужно сделать, это определить, объект подобен кортежу. Если это так, вы хотите распечатать каждый элемент кортежа. Это дает вам поддержку std::map и std::unordered_map. Обратите внимание, что std::array является как кортежем, так и итерабельным.

Это немного сложнее, чем обнаружение «итеративного», и больше зависит от того, с каким стандартом C ++ вы работаете, потому что подобие кортежа расширение с новыми версиями C ++. Вы можете полениться и просто обнаружить std::pair и std::tuple, которые охватят 99% случаев использования.

3 голосов
/ 29 мая 2020

Ваша проблема в том, что std::vector имеет более одного типа шаблона. Из-за этого вместо этого используется перегрузка T. Причина, по которой это происходит, заключается в двусмысленности языка в отношении того, как параметры шаблона по умолчанию должны учитываться в параметре шаблона шаблона. Это привело к появлению отчета о дефектах DR150 , который был принят в C ++ 17, а позволит вашему коду работать в совместимом компиляторе 1 . Чтобы обойти это, вы можете использовать параметр шаблона шаблона Variadi c и настроить базовый вариант, чтобы просто напечатать элемент, например

template<typename T>
void print(T const& elem){
    cout << elem << " ";
}

template<template<typename...> typename Cont, typename... Params>
void print(Cont<Params...> const& cont) {
    for (const auto& i : cont) {
        print(i);
    }
    cout << endl;
}

int main()
{
    vector<vector<int>> subsets{{1,2},{3,4}};
    print(subsets);
}

output

1 2 
3 4 

1: Мне пришлось настроить базовый случай, как в моем примере, потому что теперь версия шаблона temaplte будет вызывать вложенный вектор

2 голосов
/ 29 мая 2020

Вы также можете использовать SFINAE (или C ++ Concepts, если вы живете в будущем), чтобы получить желаемый результат, не зная, сколько параметров шаблона имеет ваш входящий контейнер. Вот пример использования конечных возвращаемых типов для выполнения SFINAE:

#include <iostream>
#include <vector>

template<typename T>
auto print( const T& cont ) -> decltype(std::cout << *begin(cont), void()) 
{
    for( auto&& i : cont )
        std::cout << i << ' ';
    std::cout << '\n';
}

template<typename T>
auto print( const T& cont ) -> decltype(begin(*begin(cont)), void())
{
    for( auto&& i : cont )
        print(i);
}

int main()
{
    const auto subset1  = std::vector<int>{ 1, 2, 3, 4 };
    const auto subset2 = std::vector<std::vector<int>>{ {5,6,7}, {8,9} };
    const auto subset3 = std::vector<std::vector<std::vector<int>>>{ 
        { {10,20,30}, {40,50} }, 
        { {60}, {70,80,90}, {100,110,120} },
        { {200,400,600} }
    };

    print( subset1 );
    print( subset2 );
    print( subset3 );
}

Посмотреть в реальном времени Coliru , где он выводит:

1 2 3 4 
5 6 7 
8 9 
10 20 30 
40 50 
60 
70 80 90 
100 110 120 
200 400 600 

Обратите внимание, как первая функция выражает, что для нее требуется возможность записи одного элемента с помощью функции begin() (неявно используемой циклами range-for), а вторая требует как минимум дважды вложенных последовательностей, доступных через begin().

Я бы, вероятно, склонился к шаблону вариади c, если только вам не нужно обрабатывать контейнеры отдельно от контейнеров контейнеров (контейнеров (контейнеров ...)).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...