Как реализовать функцию, которая принимает любой тип контейнера? - PullRequest
1 голос
/ 04 ноября 2019

Я хочу реализовать функцию, которая принимает std::vector или std::array в качестве аргумента. Как список параметров может абстрагироваться от типа контейнера?

См. Этот пример:

// how to implement this?
bool checkUniformity(container_type container)
{
    for(size_t i = 1; i < container.size(); i++)
    {
        const auto& o1 = container[i-1];
        const auto& o2 = container[i];

        if(!o1.isUniform(o2))
            return false;
    }

    return true;
}

struct Foo
{
    bool isUniform(const Foo& other);
}

// I want to call it in both ways:
std::vector<Foo> vec;
std::array<Foo> arr;

bool b1 = checkUniformity(vec);
bool b2 = checkUniformity(arr);

Какой самый лучший и наиболее читаемый способ сделать это?

Любые предложения по улучшению кода (стиль, дизайн) приветствуются какЧто ж. Спасибо!

Ответы [ 4 ]

6 голосов
/ 04 ноября 2019

Вы хотите template:

template <typename container_type>
bool checkUniformity(const container_type& container)
{
    for(size_t i = 1; i < container.size(); i++)
    {
        const auto& o1 = container[i-1];
        const auto& o2 = container[i];

        if(!o1.isUniform(o2))
            return false;
    }
    return true;
}
4 голосов
/ 04 ноября 2019

Если вы используете итераторы и диапазоны вместо прямой работы с контейнером, вы можете создать алгоритм, который эффективно работает с любым контейнером (включая связанные списки), а также с потоками:

#include <list>
#include <array>
#include <vector>
#include <iterator>

template <typename T>
bool checkUniformity(T begin, T end) {
    // Check for empty range
    if (begin == end) return true;

    // Remember last element
    T last = begin;
    while (++begin != end) {
        if (!((*last).isUniform(*begin)))
            return false;
        last = begin;
    }

    return true;
}

template <typename T, typename F>
bool checkUniformity(T begin, T end, F pred) {
    // Check for empty range
    if (begin == end) return true;

    // Remember last element
    T last = begin;
    while (++begin != end) {
        if (!pred(*last, *begin))
            return false;
        last = begin;
    }

    return true;
}

struct Foo
{
    bool isUniform(const Foo& other) const;
};


int main () {
    // I want to call it in both ways:
    std::vector<Foo> vec;
    std::array<Foo, 3> arr;
    std::list<Foo> list;
    Foo carr [3];

    bool b1 = checkUniformity(std::cbegin(vec), std::cend(vec));
    bool b2 = checkUniformity(std::cbegin(arr), std::cend(arr));
    bool b3 = checkUniformity(std::cbegin(list), std::cend(list));
    bool b4 = checkUniformity(std::cbegin(carr), std::cend(carr));

    bool b1_2 = checkUniformity(std::cbegin(vec), std::cend(vec), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
    bool b2_2 = checkUniformity(std::cbegin(arr), std::cend(arr), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
    bool b3_2 = checkUniformity(std::cbegin(list), std::cend(list), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
    bool b4_2 = checkUniformity(std::cbegin(carr), std::cend(carr), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
}

Вы также можетереализовать второй вариант, как показано, где вы можете указать условие как предикат (например, лямбда, как показано), если у вас есть разные варианты isUniform. Потребность передать два параметра для диапазона вместо просто контейнера немного более громоздка, но намного более гибка;он также позволяет запускать алгоритм в поддиапазоне контейнера.

Это тот же подход, который используется алгоритмами стандартной библиотеки, такими как std::find.

4 голосов
/ 04 ноября 2019

Для приема практически любого типа контейнера рекомендуется использовать шаблоны с параметрами шаблона шаблона. Большинство контейнеров в C ++ принимают в качестве первого параметра шаблона тип значения, которое они содержат, и тип распределителя, который используется для выделения памяти.

Чтобы проверить, является ли тип значения контейнера, реализует определенный метод, isUniform() в вашем случае вы можете использовать std::enable_if.

#include <iostream>
#include <vector>
#include <type_traits>

struct Foo
{
    bool isUniform(const Foo&) const { return true; }
};

//Template template parameter TContainer, that accepts 2 template parameters
//the first for the value_type, the second for the allocator type
template <template <typename, typename> typename TContainer, typename TValue, typename TAllocator>
auto checkUniformity(TContainer<TValue, TAllocator>& container)
//Using `std::enable_if` to check if the result of invoking the `isUniform()` method is bool
//in case it is not bool, or the method does not exist, the `std::enable_if_t` will result
//in an error
-> std::enable_if_t
<
    std::is_same_v
    <
        decltype(std::declval<TValue>().isUniform(std::declval<TValue>())), 
        bool
    >, 
    bool
>
{
    for(size_t i = 1; i < container.size(); i++)
    {
        const auto& o1 = container[i-1];
        const auto& o2 = container[i];

        if(!o1.isUniform(o2))
            return false;
    }

    return true;
}

int main()
{
    std::vector<Foo> vec(10);
    std::cout << std::boolalpha << checkUniformity(vec);

    return 0;
}

Обратите внимание, что std::array не имеет типа распределителя, поэтому этот метод не будет работать с std::array. Для этого вы можете изменить TContainer на простой параметр типа шаблона и использовать typename TContainer::value_type везде, где бы вы использовали TValue.

0 голосов
/ 04 ноября 2019

U может также выполнять перегрузку функций, на тот случай, если вы не хотите использовать шаблоны.

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