网站首页> 文章专栏> 从表达式左边推导模板参数
最近社区有人说rust 可以实现一些 C++ 模板做不到的事,比如这样:
fn func() -> T {
T::default()
}
let i:i32 = func(); // 根据表达式左边对象的类型推导函数的返回类型
而在C++ 里,则无法做到:
template<typename T>
T func() {
return T{};
}
int a = func(); // 无法推导T
bool b = func(); // 无法推导T
看起来好像C++ 模板做不到这一点,但实际上并不是,C++ 要实现类似的功能其实非常简单。
比如要实现一个解析方法,输入字符串,然后根据表达式左边对象类型做解析
std::string str = "42";
int i = parse(str);
assert(i == 42);
std::string s = parse(str);
assert(s == "42");
C++ 是怎么做的呢?
class parse {
public:
parse(std::string str) : str_(str) {}
template<typename T>
operator T() && {
if constexpr (std::is_integral_v<T>) {
T t = atoi(str_.data());
return t;
}
else {
return str_;
}
}
private:
std::string str_;
};
这几行代码就可以实现通过表达式左边类型去推导模板参数了,关键点在于operator T() && 转换函数,它将parse对象转换为对应的模板类型,从而实现了rust 的那种用法。
在泛型模板编程这块C++ 毫无疑问是最强的,其它语言在模板方面玩的花活,C++ 早就玩过了,还玩得更好。
地址: www.purecpp.cn
转载请注明出处!
请问parse中的T推导规则是什么?我尝试在代码中使用,但除了std::string=parse(str);的这种用法,其它的都不正确.
编译器为 vs2022 msvc /std:c++latest
// 先声明再赋值 /*std::string s; s = parse(str);*///operator=不明确operator =(const _Elem)或operator =(const _Elem *const )... // 花括号初始化 std::string s{ parse(str) };//T推导为char
直接 std::string x = parse(content); 走的是拷贝初始化,没有重载决议问题,T 推导成 std::string。
先声明再赋值调用的是 operator=() 运算符重载,需要重载决议,只要赋值运算符有多个重载,T 就有二义性。
type y{parse(content)}; 走列表初始化(list-initialization):
1. 看 type 是不是聚合类型(aggregate-type),如果是就走聚合初始化
2. 如果 type 是 std::initializer_list,那么直接构造或者拷贝构造
3. 做两阶段判断:
a. 先对所有可以只接受一个 std::initializer_list 参数的构造函数(后续参数需要有默认值)做重载决议
b. a全部失败后对所有构造函数做重载决议
std::string y{parse(content)} 显然不符合 12,但同时符合 3.ab 这两个条件。
由于 3.a 优先于 3.b,所以 T 被推导成 std::initializer_list<char> 的参数 char。
要让例子成立,需要想办法让 3.a 重载决议失败,所以需要对 operator T() 做 sfinae 约束(constraint)。
约束需要禁止 T 被推导成 char,既 !std::same_as<T, char>。
要让例子 std::string z; z = parse(content); 成立,需要让 operator=() 重载决议中的所有情况刚好只有一个匹配成功。
由于 std::string 的 operator=() 里唯一的整形重载参数是 char,在上面已经禁用掉了,所以只需要按照例子里的两种分支情况允许(std::is_integral_v<T> || std::same_as<T, std::string>)通过即可。
parse(content) 返回模板同时 std::string 的 operator=() 或者构造函数接收模板的情况不会参加推导,但 sfinae。
约束后的函数声明:
template<typename T>
requires (!std::same_as<T, char> && (std::same_as<T, std::string> || std::is_integral_v<T>))
operator T(){return {};}
purecpp
一个很酷的modern c++开源社区
purecpp社区自2015年创办以来,以“Newer is Better”为理念,相信新技术可以改变世界,一直致力于现代C++研究、应用和技术创新,期望通过现代C++的技术创新来提高企业生产力和效率。
社区坚持只发表原创技术文章,已经累计发表了一千多篇原创C++技术文章;
组织了十几场的C++沙龙和C++大会,有力地促进了国内外C++开发者之间的技术交流;
开源了十几个现代C++项目,被近百家公司所使用,有力地推动了现代C++在企业中的应用。
期待更多的C++爱好者能参与到社区C++社区的建设中来,一起为现代C++开源项目添砖加瓦,一起完善C++基础设施和生态圈。
微信公众号:purecpp, 社区邮箱: purecpp@163.com
少了返引号