来源c++20高级编程
std::declval
declval不是语言特性, 非关键字, 而是模板函数
考虑场景: 需要函数对其参数进行调用得到的返回类型
假如有下类
struct F
{
double operator()(char,int);
float operator()(int);
};
并实现
template<typename F,typename ...Args>
using InvokeResultFunc= ? ;//如何实现
总之我们希望得到以下结果
using T1=InvokeResultFunc<F,char,int>; //T1=double using T2=InvokeResultFunc<F,char>; //T2=float
一种初步实现
template<typename F,typename ...Args> using InvokeResultFunc= decltype(F{}(Args{}...)) ;
对F进行实例化得到函数对象, 再对每个参数进行实例化,
最后通过decltype操作获取调用后的返回类型
需要注意的是实例化并不是在真正的内存上构造出对象, 仅在编译期非求值于上下文中, 仅用于构造合法的语句
decltype sizeof表达式里属于非求值上下文, 例
int x=1; int y=sizeof(x++); decltype(x++) z=1; cout<<( x )<<endl; //x=1,
最后x=1
sizeof和decltype中的x仅用于求其大小或类型, 不会对x++进行真正运算
然而
当类模板参数 或 函数对象不可构造时, 其中或没有默认构造函数, 或构造函数是私有的, 总之就是无法构造F{}(Args{}...)时, 上述实现不可用(即虽然在非求值上下文里不会真正构造, 但仍需符合语法规则)
以下代码将会报错( 参数和函数对象的构造函数私有, 无法构造)
template<typename F,typename ...Args>
using InvokeResultFunc= decltype(F{}(Args{}...)) ;//如何实现
class A
{
private: //参数不可构造
A(){}
};
struct F
{
private: //函数对象不可构造
double operator()(A,int);
float operator()(int);
};
using T1=InvokeResultFunc<F,char,int>;
这时可以用declval构造, 将不受以上约束
template<typename F,typename ...Args>
using InvokeResultFunc= decltype(declval<F>()(declval<Args>()...));
第一个declval构造函数对象F, 第二个declval构造参数对象
实际上declval是返回一个转发引用的对象(T&&), 主要因为引用类型可以是非完备类型, 即只需声明, 无需定义, 如
class A; //非完备类型 class B{}; //完备类型
declval只能用于decltype, sizeof等非求值上下文中
以下实现确保程序员只能在非求值上下文中使用declval
首先定义declval_(加个下划线避免与std::declval重名)
template<typename T>
T && declval_();
但实际上只有声明, 没有定义, 这正好符合要求:
由于在非求值上下文中使用该函数不会真正调用该函数(只需取得其返回类型), 而在其他地方使用会出现declval模板函数未定义的链接错误(由于没有实现)
template<typename T>
T && declval_();
int main()
{
//链接错误: undefined reference to `int&& declval_<int>()'
//declval_<int>();
//可以使用
decltype(declval_<int>());
}
这个错误信息不够友好, 将其修改添加错误信息
struct declval_protector
{
static constexpr bool value=false;
};
template<typename T>
T && declval_()
{
static_assert(declval_protector<T>::value,
"declval只能在sizeof/declval等非求值上下文中使用" );
}
没有直接将static_assert的条件设置为false, 当在非求值上下文里使用时将会忽略跳过该断言
其他场景使用则会出现错误
(这个实现对于void而言仍有问题, 由于void没有引用类型void&&)