如何判断C++模板类型是否为C风格字符串

21

我正在尝试编写一个名为is_c_str的模板,以测试类型是否为c风格字符串。 我需要这个模板来尝试编写一个STL容器迭代器的模板特化中提到的to_string函数。

我需要区分c_str和其他类型的指针和迭代器,以便我可以按字面值表示第一个,并将指针/迭代器呈现为不透明的"itor"或"ptr"。 代码如下:

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

然而,is_c_str 不仅可以捕获 const char *,还可以捕获 intdouble。上述代码的输出结果为:

1, 1, 1
(截至gcc-4.8.1版本)。
我的问题是如何修复is_c_str以正确捕获C风格字符串?

2
remove_reference<remove_cv<int>> 不就是 char * 吗?是的,没错。 - chris
2
为什么你需要这个?也许有更简单的方法来解决你原来的问题。 - Neil Kirk
9
请注意,char* 只是一个指向 char 的指针。只有当它指向以 null 结尾的数组时,它才是 C 风格的字符串。 - user3553031
6
请注意,wchar_tchar16_tchar32_t也可以用于C风格字符串,并且实际上已经在使用中。 - dyp
3
针对你的特定用例,我建议添加一个 template<class T> to_string(T * str); 的重载函数,并使用 SFINAE 进行限制,除非 std::decay_t<T> 是其中之一:charwchar_tchar16_tchar32_t - T.C.
显示剩余6条评论
5个回答

22

您想检查类型是否与char *相同,但您正在否定std::is_same的结果,这显然不会产生正确的结果。所以让我们把它移除。

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
> {};
然而,现在这将导致输出为0, 0, 0。问题在于remove_cv去除了顶层cv限定符,但char const *中的const并非顶层限定符。


如果您想同时匹配char *char const *,最简单的解决方法是:

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

上述版本仍无法匹配char[]。如果您想要匹配它们,并减少组合std::remove_referencestd::remove_cv的冗长性,请改用std::decay

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

你可以使用std::decay_t来进一步减少冗长。 - Felix Glas
1
@Snps 这个问题没有标记 [tag:c++1y],否则我同意你的观点。 - Praetorian
如果一个参数不是字符串数组类型(即不是字符串成员类型的数组),那么你可以轻松地证明它不是C风格字符串。但如果它是未知长度的字符串成员类型的数组,则无法这样做。此外,对于指向字符串的指针,问题更加复杂:你还需要知道元素数量。 - Deduplicator

9
我试过这个方法,似乎有效:
#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;
}

很显然,列举每种情况并不像在std::integral_constant中使用通用谓词那样优雅。但是另一方面,这个谓词对于像我这样的白痴来说是外语,而“暴力”模板特化更加容易理解,在这种情况下也是可行的,因为只有少数特化。


1
实际上还有其他答案没有涉及到的内容,例如 wchar_tcharunsigned charchar16_tchar32_t - Rapptz
@Rapptz 我研究了一下,没有 std::is_character_type,所以你只能硬编码这样的列表。 - Potatoswatter
@Potatoswatter 是的,说实话这很遗憾。像这样的特性很容易自己定义,但标准库没有提供一个还是有点悲哀的。 - Rapptz
还可以使用std::true_type和std::false_type。 - Dominik Grabiec

8

sz的类型是char const*,但std::remove_cv<>只会移除顶层的const,因此你无法通过它来获取char*。相反,在使用std::decay<>完全分解类型后,您可以检查是否为char const*

namespace detail
{
    template<class T>
    struct is_c_str : std::is_same<char const*, T> {};
}

template<class T>
struct is_c_str : detail::is_c_str<typename std::decay<T>::type> {};

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

您还错误地否定了条件。我也已经修复了这个问题。

Live Example


sz 的类型是 const char *,因为 auto 被指定为衰减数组。 - chris
@Deduplicator和+chris:没错。谢谢你们。 - David G

4

已经有一些解决方案了,但由于最简单的解决方案非常简单,所以我将在这里记录下来。

template< typename, typename = void >
struct is_c_str
    : std::false_type {};

template< typename t >
struct is_c_str< t *,
    typename std::enable_if< std::is_same<
        typename std::decay< t >::type,
        char
    >::value >::type
>
    : std::true_type {};

当然,棘手的部分是分析指针类型内部的内容而不是指针类型本身。


2
有几个问题。
  1. The line

    !std::is_same<char *, typename std::remove_reference<typename std::remove_cv<T>::type>::type>::value
    

    needs to be

    std::is_same<char *, typename std::remove_reference<typename std::remove_cv<T>::type>::type>::value
    
  2. Your logic of using typename std::remove_reference<typename std::remove_cv<T>::type>::type>::value is flawed. It does not convert char const* to char*. It can convert char* const to char*.

您需要的是:

以下是必需品:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接