0

I would like to write a generic function in C++

template<class T>
string GetDebugString(const T& t);

which can universally gets debug string for all types. My problem is how to use traits to distinguish between different types:

1) Primitive types and strings: get its string representation directly.

2) Struct and class type: call its "string DebugString()" method. I would implement this method for all classes. The method would possiblely call GetDebugString recursively.

3) Pointers and smart pointers: Dereference them and follow 1) or 2)

4) Collections like vector, set or map: Iterate over all its element, get debug string for each element following 1), 2) and 3) and assemble them in some formats.

How can we do this with std::enable_if ?

yuefengz
  • 3,338
  • 1
  • 17
  • 24
  • You can use SFINAE to check whether you're handling a type with a `DebugString` method, and overloading for just about everything else you're mentioned. No traits necessary. If you search on those terms you should be able to copy/scratch together something and ask for help with specific issues. – Tony Delroy Oct 09 '14 at 17:32
  • That could help you : http://stackoverflow.com/questions/4850473/pretty-print-c-stl-containers – Chnossos Oct 09 '14 at 17:32
  • @TonyD That would be the direction I would take as well. Overloads pick up 3 and 4 (since they will be more specialized), then SFINAE to distinguish between 1 and 2. (FWIW: I'd also provide a special overload for strings, since I'd want to put them in quotes, and possibly insert escape sequences where necessary. Otherwise, a string in the middle could result in some very confusing output.) – James Kanze Oct 09 '14 at 17:41

2 Answers2

1

One way would be to use specialization to write type specific implementations. For example:

template<>
string GetDebugString(int& t)
{
   return "It's an int";
}

template<>
string GetDebugString(string& t)
{
   return t;
}

template <class T> 
string GetDebugString(T& t)
{
   //Now that you've taken care of the special cases
   //write a function to handle the generic

}
ventsyv
  • 3,316
  • 3
  • 27
  • 49
1

Roughly like this:

#include <iostream>

#define AUTO_RETURN(...) -> decltype(__VA_ARGS__) {return (__VA_ARGS__);}

std::string GetDebugString(char ch) {return {ch};}
std::string GetDebugString(char const* str) {return str;}
std::string GetDebugString(std::string const& str) {return str;}

template <typename T>
auto GetDebugString( T t ) AUTO_RETURN(std::to_string(t))

// No AUTO_RETURN here because of GCC
template <typename T>
auto GetDebugString( T ptr ) -> decltype(GetDebugString(*ptr))
{
    return "Pointer to " + GetDebugString(*ptr);
}

template <typename T>
auto GetDebugString( T&& t ) AUTO_RETURN(std::forward<T>(t).DebugString())

template <typename T, typename U>
auto GetDebugString( std::pair<T, U> const& p ) AUTO_RETURN('<' + GetDebugString(p.first) + ',' + GetDebugString(p.second) + '>')

template <typename Range>
auto GetDebugString( Range&& c ) -> decltype(std::begin(c), std::end(c), std::string())
{
    // Size obtainment can be optimized for containers with size member-functions
    std::string str = "Range with " + std::to_string(std::distance(std::begin(c), std::end(c))) + " elements: \n\t";
    for (auto const& elem : c)
        str += GetDebugString(elem) + ", ";
    return str;
}

int main()
{
    int i = 23;
    auto ptr = &i;
    char const str[] = "Hallo!";
    auto list = {4, 5, 5, 6, 8};

    std::cout << GetDebugString(i) << '\n';
    std::cout << GetDebugString(ptr) << '\n';
    std::cout << GetDebugString(str) << '\n';
    std::cout << GetDebugString(list) << '\n';
}

Demo. Note that you may have to introduce rankings to avoid ambiguities; for instance when a type has both the DebugString method as well as begin/end member functions. I didn't use ADL range-access for simplicity.

Columbo
  • 60,038
  • 8
  • 155
  • 203