Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to tell if a C++ template type is C-style string

I am trying to write a template is_c_str to test if a type is a c-style string. I need this as an attempt to write a to_string function, as shown in my other question here: Template specialization for iterators of STL containers?.

I need to tell apart c_str and other types of pointers and iterators, so that I can represent the first at the face value, and render pointers/iterators as an opaque "itor" or "ptr". The code is as follows:

#include <iostream>
template<class T>
struct is_c_str
  : std::integral_constant<
  bool,
  !std::is_same<char *, typename std::remove_reference<typename std::remove_cv<T>::type>::type>::value
> {};

int main() {
  auto sz = "Hello";  //Or: const char * sz = "Hello";
  int i;
  double d;
  std::cout << is_c_str<decltype(sz)>::value << ", "
        << is_c_str<decltype(i)>::value << ", "
        << is_c_str<decltype(d)>::value << std::endl;
}

However, is_c_str captures not only const char *, but also int and double. The above code outputs:

1, 1, 1

(as of gcc-4.8.1).

My question is how to fix is_c_str to properly capture c-style strings?

like image 681
thor Avatar asked Jul 20 '14 21:07

thor


2 Answers

You want to check if the type is the same as a char *, but you're negating the result of std::is_same, which is clearly not going to produce the correct result. So let's remove that.

template<class T> struct is_c_str   : std::integral_constant<       bool,       std::is_same<char *, typename std::remove_reference<typename std::remove_cv<T>::type>::type>::value > {}; 

However, this is now going to result in the output 0, 0, 0. The problem now is that remove_cv removes top-level cv-qualifiers, but the const in char const * is not top-level.


If you want to match both char * and char const * the easiest solution is:

template<class T> struct is_c_str   : std::integral_constant<       bool,       std::is_same<char *, typename std::remove_reference<typename std::remove_cv<T>::type>::type>::value ||       std::is_same<char const *, typename std::remove_reference<typename std::remove_cv<T>::type>::type>::value > {}; 

The above version will still not match char[]. If you want to match those too, and reduce the verbosity of combining std::remove_reference and std::remove_cv, use std::decay instead.

template<class T> struct is_c_str   : std::integral_constant<       bool,       std::is_same<char const *, typename std::decay<T>::type>::value ||       std::is_same<char *, typename std::decay<T>::type>::value > {}; 
like image 180
Praetorian Avatar answered Oct 05 '22 03:10

Praetorian


I tried this and it seems to work:

#include <iostream>

template<class T>
struct is_c_str : std::integral_constant<bool, false> {};

template<>
struct is_c_str<char*> : std::integral_constant<bool, true> {};

template<>
struct is_c_str<const char*> : std::integral_constant<bool, true> {};

int main() {
  auto sz = "Hello";
  int i;
  double d;
  std::cout << is_c_str<decltype(sz)>::value << ", "
        << is_c_str<decltype(i)>::value << ", "
        << is_c_str<decltype(d)>::value << std::endl;
}

Obviously enumerating every case is not as elegant as putting the general predicate in std:integral_constant, but on the other hand that predicate is alien tongue for idiots like me, while the "brute force" template specialization is somewhat more comprehensible, and viable in this case as there are few specializations.

like image 30
gpeche Avatar answered Oct 05 '22 03:10

gpeche