std::declval用于编译期“假装”存在对象以获取其类型,不构造、不求值,仅适用于decltype/sizeof/noexcept等非求值上下文,返回t&&,常配合decltype推导成员类型或用于sfinae。

std::declval 用来干啥:在编译期“假装”有个对象,好取它的类型
它不构造对象、不调用构造函数、甚至不求值,纯粹是给类型推导“搭个架子”。常见于 decltype 配合使用,比如你想知道 T::value_type 是什么,但 T 是个抽象类或没默认构造函数——这时候 std::declval<t>()</t> 就能帮你“凭空”拿出一个 T&& 来喂给 decltype。
典型错误现象:error: use of deleted function 'A::A()' 或 error: invalid use of incomplete type,往往是因为你写了 decltype(A{}.member) 却忘了 A 根本不能实例化。
-
std::declval只能在非求值上下文(unevaluated context)里用,比如decltype、sizeof、noexcept里 —— 写在普通表达式里会编译失败 - 返回的是
T&&,不是T;如果要模拟左值,得套一层std::declval<t>()</t> - 对
void类型调用std::declval<void>()</void>是合法的,但返回void&&,只能用于decltype等不真正“用到值”的地方
怎么写才不会触发构造或崩溃
关键就一条:只让它活在 decltype 里。别的地方别裸用。
比如想提取容器的迭代器类型:
立即学习“C++免费学习笔记(深入)”;
template<typename C> using iterator_t = decltype(std::declval<C&>().begin());
这里 std::declval<c>()</c> 给出一个左值引用,保证 .begin() 调用符合重载规则;如果写成 std::declval<c>()</c>,得到的是右值,可能匹配到移动版 begin()(虽然少见),也可能让某些只支持左值的类型直接 SFINAE 失败。
- 永远用
std::declval<t>()</t>模拟左值,std::declval<t>()</t>模拟右值,按成员函数的 cv/ref-qualifier 匹配需要来选 - 不要对未定义类型(incomplete type)调用成员访问,比如
std::declval<t>().x</t>前T还没定义完整,会报错;先确保类型可见 - 别在函数体里写
auto x = std::declval<int>();</int>—— 这不是编译期技巧,是运行时非法操作,直接编译失败
和 sizeof/decltype/noexcept 的配合要点
这三个是 std::declval 最常混搭的场景,因为它们都属于“看类型、不执行”的上下文。
例如判断某类型是否有 size() 成员函数:
template<typename T> constexpr bool has_size_v = noexcept(std::declval<T&>().size());
这里 noexcept 只检查表达式是否可能抛异常,不真调用 size();std::declval<t>()</t> 提供了合法左值,让重载决议能走通。
-
sizeof(std::declval<t>())</t>是合法的,但几乎没用 —— 它只返回sizeof(T),不如直接写sizeof(T) -
decltype(std::declval<t>())</t>就是T&&,一般没必要这么绕;重点还是用在带点号或箭头的表达式里 - 在模板中做 SFINAE 时,
std::declval是安全的“占位符”,但它本身不参与重载决议,只是帮表达式形成合法语法
容易被忽略的细节:返回类型和 cv 限定符
std::declval<const t>()</const> 返回 const T&&,不是 const T&。这意味着如果你要测试 const 成员函数,必须显式写 std::declval<const t>()</const> 或 std::declval<const t>()</const>,否则可能匹配不到。
比如:
struct X { void f() const; };
// 下面这行会失败:std::declval<X>() 是 X&&,f() const 不可被非常量右值调用
// decltype(std::declval<X>().f()) // error
// 正确写法:
decltype(std::declval<const X&>().f()) // ok
-
std::declval本身不接受 cv 限定符以外的修饰,比如std::declval<t>()</t>是错的;它只接受T、T&、const T&、T&&等顶层 cv/ref 形式 - 模板参数推导时,
std::declval<t>()</t>的返回类型会影响整个表达式的值类别,进而影响decltype结果是T、T&还是T&& - 它不处理数组退化、函数转指针这些隐式转换,所以
std::declval<int>()</int>返回int(&)[3],不是int*










