问题描述
标准库实用程序 declval
为 defined 为:
template<class T> add_rvalue_reference_t<T> declval() noexcept;
在这里添加右值引用似乎是个好主意,如果您考虑一下 C++11 中引入的语言:返回值涉及一个临时的,后来被转移了。 现在 C++17 引入了保证复制省略,这不再适用。正如cppref所说:
纯右值和临时值的 C++17 核心语言规范是 与早期的 C++ 修订版根本不同:有 不再是复制/移动的临时对象。另一种描述方式 C++17 机制是“非物化值传递”:纯右值是 返回并使用,从未实现临时。
这对在 declval
方面实现的其他实用程序有一些影响。看看这个例子(查看 godbolt.org):
#include <type_traits>
struct Class {
explicit Class() noexcept {}
Class& operator=(Class&&) noexcept = delete;
};
Class getClass() {
return Class();
}
void test() noexcept {
Class c{getClass()}; // succeeds in C++17 because of guaranteed copy elision
}
static_assert(std::is_constructible<Class,Class>::value); // fails because move ctor is deleted
这里我们有一个 nonmovable 类。由于保证复制省略,它可以从函数返回,然后在 test()
中本地实现。然而 is_construtible
类型特征表明这是不可能的,因为它是 defined 就 declval
:
模板特化的谓词条件
is_constructible<T,Args...>
应满足当且仅当
以下变量定义对于某些发明的人来说是格式良好的
变量 t
:T t(declval<Args>()...);
因此在我们的示例中,类型特征声明是否可以从返回 Class
的假设函数构造 Class&&
。任何当前类型特征都无法预测 test()
中的行是否允许,尽管命名表明 is_constructible
确实如此。
这意味着,在保证复制省略实际上可以挽救这一天的所有情况下,is_constructible
会误导我们,告诉我们“它是否可以在 C+ 中构造 +11?”。
这不仅限于 is_constructible
。使用(查看 godbolt.org)
void consume(Class) noexcept {}
void test2() {
consume(getClass()); // succeeds in C++17 because of guaranteed copy elision
}
static_assert(std::is_invocable<decltype(consume),Class>::value); // fails because move ctor is deleted
这表明 is_invocable
受到了类似的影响。
对此最直接的解决方案是将 declval
更改为
template<class T> T declval_cpp17() noexcept;
这是 C++17(及后续,即 C++20)标准中的缺陷吗?或者我是否忽略了为什么这些 declval
、is_constructible
和 is_invocable
规范仍然是我们可以拥有的最佳解决方案?
解决方法
然而 is_construtible
类型特征表明这是不可能的,因为它是根据 declval
定义的:
Class
不是可构造的从它自己类型的实例。所以is_constructible
不应该说它是。
如果类型 T
满足 is_constructible<T,T>
,则期望您可以在给定类型为 T
的对象的情况下创建 T
,不您可以专门从 T
类型的纯右值生成 T
。这不是使用 declval
的怪癖;这就是问题 is_constructible
的意思。
您的建议是,is_constructible
应该回答与它打算回答的问题不同的问题。应该注意的是,保证省略意味着所有类型都可以从其自身类型的纯右值“构造”。所以如果这就是你想问的,那么你已经有了答案。
std::declval
函数主要用于转发。举个例子:
template<typename... Ts>
auto f(Ts&&... args) -> decltype(g(std::declval<Ts>()...)) {
return g(std::forward<Args>(args)...);
}
在这种常见情况下,让 std::declval
返回纯右值是错误的,因为没有好的方法可以转发纯右值。