我继承了一些看起来像这样的代码:
///
/// 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
始终为真吗?
答案 0 :(得分:9)
您发布的代码形成错误,无需诊断。
用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 ++标准要求所有模板(包括主模板)必须具有有效的实例化(在标准中称为有效的特化)。 (还有其他要求,例如,如果涉及包,至少有一个这样的实例化必须有非空包装。)
您可能会抱怨&#34;它已编译并正常工作&#34;,但无需诊断表示。 C ++标准将零约束置于编译器遇到“错误形成的无需诊断”的情况下所做的事情。案件。它无法检测到它,并轻率地编译它&#34;工作&#34;。它可以假设它是不可能的,并且如果它确实发生则生成格式错误的代码。它可以尝试检测它,失败并执行上述任一操作。它可以尝试检测它,成功并生成错误消息。它可以检测到它,成功并生成代码,用于通过电子邮件将您在浏览器中查看过的每张图像的缩略图通过电子邮件发送给所有联系人。
形象错误,无需诊断。
我会自己避免使用这些代码。
现在,有人可能会争辩说某人可能会专门is_same<T,T>
返回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_string
和from_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)
即可。
基础反弹到标签调度。要进行自定义,请重载标记 - 调度版本。
要实现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_string
重载的情况下致电from_string
或tag_t
,您最终会调用=delete
并且未找到&#34;过载&#34;错误。
测试代码:
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;
}
(不需要使用friend
,该函数必须与Bob
或namespace utility
内的名称空间相同。