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?
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 > {};
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With