C++ как различать шаблон для контейнера и собственный тип

у меня следующая проблема:

template<class T>
void set(std::string path, const T data)
{
   stringstream ss;
   ss << data << std::endl;
   write(path, ss.str();
}

template<class T>
void set(std::string path, const T data)
{
    std::stringstream ss;
    for(typename T::const_iterator it = data.begin(); it < data.end(); ++it)
    {
       ss << *it;
       if(it < data.end() -1 )
          ss << ", ";
    }
    ss << std::endl;
    write(path, ss.str());
}

Я получаю следующую ошибку:

error: ‘template<class T> void myclass::set(std::string, T)’ cannot be overloaded
error: with ‘template<class T> void myclass::set(std::string, T)’

есть ли способ различать типы контейнеров и другие типы в шаблонах?

4 ответов


В C++03 вы можете сделать это с помощью небольшого количества SFINAE, чтобы выборочно включить разные версии функции для разных типов:

#include <boost/type_traits.hpp>
#include <sstream>
#include <iostream>
#include <vector>

using namespace std;

template<class T>
void set(typename boost::enable_if<boost::is_pod<T>, std::string>::type path, const T data)
{
   std::cout << "POD" << std::endl;
   stringstream ss;
   ss << data << std::endl;
}

template<class T>
void set(typename boost::disable_if<boost::is_pod<T>, std::string>::type path, const T data)
{
    std::cout << "Non-POD" << std::endl;
    std::stringstream ss;
    for(typename T::const_iterator it = data.begin(); it < data.end(); ++it)
    {
       ss << *it;
       if(it < data.end() -1 )
          ss << ", ";
    }
    ss << std::endl;
}

int main() {
  int i;
  float f;
  std::vector<int> v;
  set("", v);
  set("", i);
  set("", f);
}

я использовал boost для удобства здесь, но вы можете свернуть свой собственный, если boost не вариант или использовать C++11 вместо этого.

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


использовать черту:

#include <type_traits>

template <typename T>
typename std::enable_if<is_container<T>::value>::type
set (std::string const & path, T const & container)
{
    // for (auto const & x : container) // ...
}


template <typename T>
typename std::enable_if<!is_container<T>::value>::type
set (std::string const & path, T const & data)
{
    std::ostringstream oss;
    oss << data;
    write(path, oss.str());
}

вы можете найти подходящую черту в довольно принтера код.


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

во-первых, вам нужна функция, чтобы определить, является ли тип элемента итератора...

template <typename T>
struct Has_Iterator
{
    template <typename>
    static char test(...);

    template <typename U>
    static int test(typename U::const_iterator*);

    static const bool result = sizeof test<T>(0) != sizeof(char);
};

в приведенном выше коде, стандарт C++ требует test(typename U::const_iterator*) для использования в предпочтении к расплывчатому "...- параметры совпадают, пока U is фактически структура / класс с const_iterator тип члена. В противном случае у вас есть "сбой замены", который не является фатальным ошибка остановки компиляции (следовательно, SFINAE), и попытка найти соответствующую функцию удовлетворяется test(...). Поскольку возвращаемые типы двух функций различаются, то sizeof оператор может проверить, какой из них был подобран, задание result логическое надлежащим образом.

затем вы можете пересылать запросы на печать вещей в специализации шаблонов, которые их поддерживают...

template <typename T>
void print(const T& data)
{
    printer<Has_Iterator<T>::result, T>()(data);
}

// general case handles types having iterators...
template <bool Has_It, typename T>
struct printer
{
    void operator()(const T& data)
    {
        for (typename T::const_iterator i = data.begin(); i != data.end(); ++i)
            std::cout << *i << ' ';
        std::cout << '\n';
    }
};

// specialisation for types lacking iterators...
template <typename T>
struct printer<false, T>
{
    void operator()(const T& data)
    {
        std::cout << data << '\n';
    }
};

как писали мои предшественники, вы должны использовать какую-то черту. Вы, вероятно, должны использовать Boost для этого, но если вы не хотите, вы можете просто использовать что-то вроде этого (http://ideone.com/7mAiB ):

template <typename T>
struct has_const_iterator {
    typedef char yes[1];
    typedef char no[2];

    template <typename C> static yes& test(typename C::const_iterator*);
    template <typename> static no& test(...);

    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};

template <bool> class bool2class {};

template <class T>
void set_inner(const std::string &path, T & var, bool2class<false> *) {
        // T is probably not STL container
}

template <class T>
void set_inner(const std::string &path, T & var, bool2class<true> *) {
        // T is STL container
}

template <class T>
void set(const std::string &path, T &var) {
        set_inner(path, var, (bool2class<has_const_iterator<T>::value>*)0);
}

это непростая задача отличить контейнер от простого массива, поэтому я использовал здесь проверку, имеет ли тип const_iterator. Вероятно, вы также должны проверить, имеет ли он begin(), end() и другие вещи, которые вы будете использовать в будущем код.