Search code examples
c++c++11operator-overloadingc++14template-argument-deduction

std::enable_if_t to separate string from non string function parameters


I need to print all kind of string data within double quotation, and others without double quotation.

This are my functions to check if the argument is a string.

template<class T>
struct is_str : std::integral_constant<bool, false> {};
template<> struct is_str<char*> : std::integral_constant<bool, true> {};
template<> struct is_str<wchar_t*> : std::integral_constant<bool, true> {};
template<> struct is_str<const char*> : std::integral_constant<bool, true> {};
template<> struct is_str<const wchar_t*> : std::integral_constant<bool, true> {};
template<> struct is_str<std::string> : std::integral_constant<bool, true> {};
template<> struct is_str<const std::string> : std::integral_constant<bool, true> {};
template<> struct is_str<std::wstring> : std::integral_constant<bool, true> {};
template<> struct is_str<const std::wstring> : std::integral_constant<bool, true> {};

And in the printer function, I use the above functions like this

template<typename T>
std::enable_if_t<is_str<T>::value>, std::ostream&>
    operator<<(std::ostream& xx, const T& ar)
{
    xx << "\"" << ar << "\"";
    return xx;
}

template<typename T>
std::enable_if_t<!is_str<T>::value>, std::ostream&>
    operator<<(std::ostream& xx, const T& ar)
{
    xx << ar;
    return xx;
}

It doesn't compile with the error

unrecognized template declaration/definition

Can someone please tell me how to fix this or better way to handle this situation?


Solution

  • Graham Best is right but there is another (bigger, IMHO) problem.

    You're not defining operator<<; you're redefining it calling itself.

    As far I know, it's impossible (you redefine a function, the compiler doesn't know which version call) and when you write (inside the redefined operator)

    xx << "\"" << ar << "\"";
    

    which version of operator<<() should be used? The new redefined (causing a loop recursion) or the old one?

    I think that the best way to exit from this problem is avoid to redefine operator<<() and define a simple function; by example, the following print()

    template<typename T>
    std::enable_if_t<is_str<T>::value, std::string> print (T const & ar)
     {
       std::ostringstream oss;
    
       oss << "\"" << ar << "\"";
    
       return oss.str();
     }
    
    template<typename T>
    std::enable_if_t<!is_str<T>::value, T const &> print (T const & ar)
     { return ar; }
    

    and use it in this way

    std::cout << print(std::string{"abc"}) << std::endl; // add quotes
    std::cout << print(1) << std::endl;                  // no quotes
    

    En passant: there are a couple of classes that can be useful to write a more compact code: std::true_type, defined as std::integral_constant<bool, true>, and std::false_type, defined as std::integral_constant<bool, false>.

    So you can define is_str as follows

    template<class T>
    struct is_str : std::false_type {};
    
    template<> struct is_str<char*> : std::true_type {};
    template<> struct is_str<wchar_t*> : std::true_type {};
    // ...
    

    OBSERVE also that if you write

    std::cout << print("abc") << std::endl; // add quotes
    

    the print() doesn't add the quotes.

    This is because "abc" isn't a char const *; it's a const char[4].

    So, if you want add the quotes to char[N] and wchar_t[N] too, you should add the following specializations

    template<std::size_t N> struct is_str<char[N]> : std::true_type {};
    template<std::size_t N> struct is_str<wchar_t[N]> : std::true_type {};