std::is_same<t,t>::value 总是正确的吗?

Is std::is_same<t,t>::value always true?

我继承了一些看起来像这样的代码:

///
/// A specializable function for converting a user-defined object to a string value
///
template <typename value_type>
std::string to_string(const value_type &value)
{
    static_assert(!std::is_same<value_type, value_type>::value, "Unspecialized usage of to_string not supported");
    return "";
}

///
/// A specializable function for converting a user-defined object from a string to a value
///
template <typename return_type>
return_type from_string(const std::string &source)
{
    static_assert(!std::is_same<return_type, return_type>::value, "Unspecialized usage of from_string not supported");
}

!std::is_same<value_type, value_type>::value 似乎过于冗长。

我应该将这些语句更改为 static_assert(false,"...") 吗?

我不确定以这种方式表达是否是为了处理某种边缘情况,或者 false 是否确实等价。

std::is_same<t,t>::value 总是正确的吗?

您发布的代码格式错误,无需诊断。

将其替换为 static_assert(false, ...) 会使编译器注意到您的代码格式不正确。代码 之前是 格式错误的,编译器只是没有注意到它。

我有两个解决你的问题。一个是黑客,但合法。另一个更简洁,但需要您编写更多代码。

此答案的第一部分是您的代码格式错误的原因。下面两个是解决方案。

为什么代码格式错误?

template <typename value_type>
std::string to_string(const value_type &value)
{
  static_assert(!std::is_same<value_type, value_type>::value, "Unspecialized usage of to_string not supported");
  return "";
}

to_string 的主模板不能用任何类型实例化。 C++ 标准要求所有模板(包括主模板)都必须具有有效的实例化(在标准语中称为有效特化)。 (还有其他要求,例如如果涉及包,则至少有一个这样的实例必须具有非空包等)。

您可能会抱怨“编译成功”,但那是不需要诊断的意思。 C++ 标准将 zero 限制编译器在遇到“格式错误,无需诊断”情况时执行的操作。它可能无法检测到它并愉快地编译“有效”。它可以假定这是不可能的,并且如果确实发生了则生成格式错误的代码。它可以尝试检测它,失败,然后执行上述任一操作。它可以尝试检测它,成功并生成错误消息。它可以检测到它,成功并生成代码,将您去年在浏览器中查看的每张图片的缩略图通过电子邮件发送给您的所有联系人。

格式错误,不需要诊断。

我自己会避免这样的代码。

现在,有人可能会争辩说有人可以在某个地方将 is_same<T,T> 专门化为 return false,但这也会使您的程序格式错误,因为它是来自 std 违反了标准中对模板的要求。

!std::is_same<value_type, value_type>::value 替换为 false 只会让您的编译器意识到您的代码格式错误,并生成一条错误消息。从某种意义上说,这是一件好事,因为格式错误的代码将来可能会以任意方式破坏。

破解方法

解决这个问题的愚蠢方法是创建

template<class T, class U>
struct my_is_same:std::is_same<T,U> {};

这承认了专业化漏洞的可能性。这仍然是代码味道。

正确的修复方法

写这两个的正确方法需要一些工作。

首先,to_stringfrom_string 基于 标签调度 而不是 模板专业化 :

namespace utility {
  template<class T>struct tag_t {};
  template <typename value_type>
  std::string to_string(tag_t<value_type>, const value_type &value) = delete;
  template <typename value_type>
  std::string to_string(const value_type &value) {
    return to_string(tag_t<value_type>{}, value);
  }

  template <typename return_type>
  return_type from_string(tag_t<return_type>, const std::string &source) = delete;
  template <typename return_type>
  return_type from_string(const std::string &source) {
    return from_string(tag_t<return_type>{}, source);
  }
}

目标是最终用户只需执行 utility::from_string<Bob>(b)utility::to_string(bob) 即可。

基础的跳转到标记调度。要自定义,您需要重载 tag-dispatch 版本。

要实现to/from字符串,在Bob的命名空间中写这两个函数:

Bob from_string( utility::tag_t<Bob>, const std::string& source );
std::string to_string( utility::tag_t<Bob>, const Bob& source );

注意它们不是模板或模板的特化。

要处理 std 中的类型或内置类型,只需在 namespace utility.

中定义类似的重载

现在,ADL 和标签调度将带您进入正确的 to/from 字符串函数。无需更改命名空间来定义 to/from 字符串。

如果您在没有有效 tag_t 重载的情况下调用 to_stringfrom_string,您最终会调用 =delete 并收到“找不到重载”错误。

测试代码:

struct Bob {
    friend std::string to_string( utility::tag_t<Bob>, Bob const& ) { return "bob"; }
    friend Bob from_string( utility::tag_t<Bob>, std::string const&s ) { if (s=="bob") return {}; exit(-1); }
};

int main() {
    Bob b = utility::from_string<Bob>("bob");
    std::cout << "Bob is " << utility::to_string(b) << "\n";
    b = utility::from_string<Bob>( utility::to_string(b) );
    std::cout << "Bob is " << utility::to_string(b) << std::endl;
    Bob b2 = utility::from_string<Bob>("not bob");
    std::cout << "This line never runs\n";
    (void)b2;
}

Live example.

(不需要使用 friend,该函数只需与 Bob 位于同一名称空间中或位于 namespace utility 内)。