6

I have the following problem:

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

I get the following error:

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

Is there a way to differentiate between container types and other types in templates?

Steve
  • 1,032
  • 10
  • 26
  • type traits. Looks like you need to make your own is_container trait (http://stackoverflow.com/questions/7617203/is-it-possible-to-use-type-traits-to-check-whether-a-type-is-a-container). (Haven't done this before, and just waiting for a build to complete, so not a complete answer - sorry. But I would be interested in the solution ;)) – peterchen Jul 19 '12 at 09:40
  • Do you want `const T & data`, and `ss << data`?? – Kerrek SB Jul 19 '12 at 09:40
  • This is *almost* a great question, but there's a few syntax errors that made answering it harder than it needed to be. (`T` should be `data` on the third line of the first form of `set` and your calls to `write` are missing a `)` but still wrong after adding that). – Flexo Jul 19 '12 at 09:48
  • Note that `it < data.end() - 1` requires not just that `T` is a container, but that it is a container with a random-access iterator. So for standard containers that's just `vector`, `deque` and in C++11 `array`, plus the various string classes could work in that template too. Also it's required that `data` is non-empty, which you certainly can't overload on ;-p – Steve Jessop Jul 19 '12 at 11:12

4 Answers4

8

Use a trait:

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

You can find a suitable trait in the pretty printer code.

Community
  • 1
  • 1
Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
6

In C++03 you can do this with a little bit of SFINAE to selectively enable different versions of the function for different types:

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

I used boost for convenience here, but you could roll your own if boost isn't an option or use C++11 instead.

is_pod isn't quite what you want really, you probably want an is_container trait, but that's not so trivial, you'll need to make a trait of your own and is_pod makes a good approximation for how to use traits to selectively enable functions as simple answer.

Community
  • 1
  • 1
Flexo
  • 82,006
  • 22
  • 174
  • 256
1

You might try Substitution Failure Is Not An Error (SFINAE) techniques here.

Firstly, you need a function to determine whether a type has an iterator member...

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

In the above code, the C++ Standard requires test(typename U::const_iterator*) to be used in preference to the vague "..." parameter match, so long as U is actually a struct/class with a const_iterator member type. Otherwise, you have a "substitution failure" - which is not a fatal error stopping compilation (hence SFINAE), and the attempt to find a matching function is satified by test(...). As the return types of the two functions differ, the sizeof operator can test which one has been matched, setting the result boolean appropriately.

Then you can forward requests to print things to template specialisations that support them...

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';
    }
};
Tony Delroy
  • 94,554
  • 11
  • 158
  • 229
0

As my predecessors wrote you have to use some kind of trait. You probably should use Boost for that, but if you don't want to you can just use something like this ( 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);
}

It's not an easy task to distinguish container from simple array, so I used here checking if type has const_iterator. Probably you should also check if it has begin(), end() and other things you would use in future code.

Pawel Zubrycki
  • 2,663
  • 15
  • 26