Подтвердить что ты не робот

Как указать тип аргумента шаблона контейнера?

Предположим, что мы имеем это template

template<typename Container, typename T> 
bool    contains (const Container & theContainer, const T & theReference) {
     ...
}

Как можно утверждать, что, очевидно, элементы в контейнере должны иметь тип T?

Может ли это быть сокращенным (возможно, в С++ 11)?

4b9b3361

Ответ 1

Вы можете ограничить тип контейнера в шаблоне:

#include <algorithm>
#include <iostream>
#include <vector>

template< template<typename ... > class Container, typename T>
bool contains(const Container<T>& container, const T& value) {
    return std::find(container.begin(), container.end(), value) != container.end();
}

int main()
{
    std::vector<int> v = { 1, 2, 3 };
    std::cout << std::boolalpha
        << contains(v, 0) << '\n'
        << contains(v, 1) << '\n';
    // error: no matching function for call to ‘contains(std::vector<int>&, char)’
    contains(v, '0') ;
    return 0;
}

Более полное решение (обращаясь к некоторым комментариям):

#include <algorithm>
#include <array>
#include <iostream>
#include <map>
#include <set>
#include <vector>


// has_member
// ==========

namespace Detail {
    template <typename Test>
    struct has_member
    {
        template<typename Class>
        static typename Test::template result<Class>
        test(int);

        template<typename Class>
        static std::false_type
        test(...);
    };
}
template <typename Test, typename Class>
using has_member = decltype(Detail::has_member<Test>::template test<Class>(0));


// has_find
// ========

namespace Detail
{
    template <typename ...Args>
    struct has_find
    {
        template<
            typename Class,
            typename R = decltype(std::declval<Class>().find(std::declval<Args>()... ))>
        struct result
        :   std::true_type
        {
            typedef R type;
        };
    };
}
template <typename Class, typename ...Args>
using has_find = has_member<Detail::has_find<Args...>, Class>;


// contains
// ========

namespace Detail
{
    template<template<typename ...> class Container, typename Key, typename ... Args>
    bool contains(std::false_type, const Container<Key, Args...>& container, const Key& value) {
        bool result = std::find(container.begin(), container.end(), value) != container.end();
        std::cout << "Algorithm: " << result << '\n';;
        return result;
    }

    template<template<typename ...> class Container, typename Key, typename ... Args>
    bool contains(std::true_type, const Container<Key, Args...>& container, const Key& value) {
        bool result = container.find(value) != container.end();
        std::cout << "   Member: " << result << '\n';
        return result;
    }
}

template<template<typename ...> class Container, typename Key, typename ... Args>
bool contains(const Container<Key, Args...>& container, const Key& value) {
    return Detail::contains(has_find<Container<Key, Args...>, Key>(), container, value);
}

template<typename T, std::size_t N>
bool contains(const std::array<T, N>& array, const T& value) {
    bool result = std::find(array.begin(), array.end(), value) != array.end();
    std::cout << "    Array: " << result << '\n';;
    return result;
}

// test
// ====

int main()
{
    std::cout << std::boolalpha;

    std::array<int, 3> a = { 1, 2, 3 };
    contains(a, 0);
    contains(a, 1);

    std::vector<int> v = { 1, 2, 3 };
    contains(v, 0);
    contains(v, 1);

    std::set<int> s = { 1, 2, 3 };
    contains(s, 0);
    contains(s, 1);

    std::map<int, int> m = { { 1, 1}, { 2, 2}, { 3, 3} };
    contains(m, 0);
    contains(m, 1);

    return 0;
}

Ответ 2

В то время как другие ответы с использованием value_type верны, каноническое решение этой частой проблемы заключается в не пропускать контейнер в первую очередь: используйте семантику стандартной библиотеки и передать пара итераторов.

Передавая итераторы, вам не нужно беспокоиться о самом контейнере. Ваш код также намного более общий: вы можете действовать на диапазонах, вы можете использовать обратные итераторы, вы можете комбинировать свой шаблон с другими стандартными алгоритмами и т.д.:

template<typename Iterator, typename T> 
bool contains (Iterator begin, Iterator end, const T& value) {
     ...
}

int main(){
    std::vector<int> v { 41, 42 };
    contains(std::begin(v), std::end(v), 42);
};

Если вы хотите проверить тип, переносимый Iterator, вы можете использовать std::iterator_traits:

static_assert(std::is_same<typename std::iterator_traits<Iterator>::value_type, T>::value, "Wrong Type");

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


Окончательный шаблон будет выглядеть так:

template<typename Iterator, typename T> 
bool contains (Iterator begin, Iterator end, const T& value) {

    static_assert(std::is_same<typename std::iterator_traits<Iterator>::value_type, T>::value, "Wrong Type");

     while(begin != end)
       if(*begin++ == value)
         return true;
     return false;
}

Живая демонстрация


Примечания:

1) Это не должно удивлять, но наш шаблон contains теперь имеет почти такую ​​же подпись, что std::find (который возвращает итератор):

template< class InputIt, class T >
InputIt find( InputIt first, InputIt last, const T& value );

2) Если изменить подпись оригинала contains слишком много, вы всегда можете переслать вызов нашему новому шаблону:

template<typename Container, typename T> 
bool contains (const Container & theContainer, const T & theReference) {
     return contains(std::begin(theContainer), std::end(theContainer), theReference);
}

Ответ 3

Для стандартного контейнера вы можете использовать value_type:

template<typename Container> 
bool contains (const Container & theContainer, const typename Container::value_type& theReference) {
     ...
}

Обратите внимание, что в вашем случае также есть const_reference:

template<typename Container> 
bool contains (const Container & theContainer, typename Container::const_reference theReference) {
     ...
}

Ответ 4

Вы можете проверить контейнер value_type и T с помощью static_assert

template<typename Container, typename T> 
bool    contains (const Container & theContainer, const T & theReference) {
     static_assert(  std::is_same<typename Container::value_type, T>::value,
                    "Invalid container or type" );
     // ...
}

Демо Here

Ответ 5

Используя std::enable_if (http://en.cppreference.com/w/cpp/types/enable_if), но немного сложнее, чем с static_assert.

EDIT: Согласно комментарию P0W, использование std::enable_if позволяет нам использовать SFINAE, что приятно, когда вы решили иметь больше перегрузок. Например, если компилятор решает использовать эту шаблонную функцию, с Container без value_type typedefed, он не будет генерировать ошибку мгновенно, например, static_assert будет, просто ищет другие функции, которые идеально подходят подписи.

Протестировано на Visual Studio 12.

#include <vector>
#include <iostream>

template<typename Container, typename T>
typename std::enable_if<
    std::is_same<T, typename Container::value_type>::value, bool>::type //returns bool
    contains(const Container & theContainer, const T & theReference)
{
    return (std::find(theContainer.begin(), theContainer.end(), theReference) != theContainer.end());
};

int main()
{
    std::vector<int> vec1 { 1, 3 };
    int i = 1;
    float f = 1.0f;
    std::cout << contains(vec1, i) << "\n";
    //std::cout << contains(vec1, f); //error
    i = 2;
    std::cout << contains(vec1, i) << "\n";
};

выход:

1
0

PS: Ваша оригинальная функция тоже делает это, за исключением того, что позволяет также создавать производные классы. Эти решения не выполняются.