Herb Sutter回归本源!CppCon上的现代 C++演示要点讨论了传递参数的不同选项,并比较了它们的性能与编写/教学的容易程度。“高级”选项(在所有测试案例中提供最佳性能,但对于大多数开发人员来说太难编写)是完美的转发,给出的示例(PDF,第 28 页):
class employee {
std::string name_;
public:
template <class String,
class = std::enable_if_t<!std::is_same<std::decay_t<String>,
std::string>::value>>
void set_name(String &&name) noexcept(
std::is_nothrow_assignable<std::string &, String>::value) {
name_ = std::forward<String>(name);
}
};
该示例使用带有转发引用的模板函数,模板参数String
使用enable_if
. 然而,约束似乎是不正确的:似乎是说只有当String
类型不是 a时才可以使用这种方法std::string
,这是没有意义的。这意味着std::string
可以使用除值之外的任何内容来设置此成员std::string
。
using namespace std::string_literals;
employee e;
e.set_name("Bob"s); // error
我考虑的一种解释是,有一个简单的错字,并且该约束旨在std::is_same<std::decay_t<String>, std::string>::value
代替!std::is_same<std::decay_t<String>, std::string>::value
. 但是,这意味着 setter 不能用于例如,const char *
并且鉴于这是演示文稿中测试的案例之一,它显然打算与这种类型一起使用。
在我看来,正确的约束更像是:
template <class String,
class = std::enable_if_t<std::is_assignable<decltype((name_)),
String>::value>>
void set_name(String &&name) noexcept(
std::is_nothrow_assignable<decltype((name_)), String>::value) {
name_ = std::forward<String>(name);
}
允许任何可以分配给成员的内容与 setter 一起使用。
我有正确的约束吗?还有其他可以改进的地方吗?对原始约束是否有任何解释,也许是断章取义?
另外我想知道这个声明中复杂的、“不可教的”部分是否真的那么有益。由于我们没有使用重载,我们可以简单地依赖正常的模板实例化:
template <class String>
void set_name(String &&name) noexcept(
std::is_nothrow_assignable<decltype((name_)), String>::value) {
name_ = std::forward<String>(name);
}
当然还有一些关于是否noexcept
真的重要的争论,有些人说除了移动/交换原语之外不要太担心它:
template <class String>
void set_name(String &&name) {
name_ = std::forward<String>(name);
}
也许对于概念来说,约束模板不会太难,只是为了改进错误消息。
template <class String>
requires std::is_assignable<decltype((name_)), String>::value
void set_name(String &&name) {
name_ = std::forward<String>(name);
}
这仍然有缺点,它不能是虚拟的,并且它必须在标题中(尽管希望模块最终会渲染那个没有实际意义的),但这似乎相当可教。