不支持C++11 decltype的噩耗
前言:因为公司现在使用vs2008,所以很多c++11的新特性还未能使用,导致写了很多冤枉代码。
最初引擎的数学库非常简单,使用起来也不方便,例如:
float FastLerp(const float& a, const float& b, float t);
vec2f FastLerp(const vec2f& a, const vec2f& b, float t);
vec3f FastLerp(const vec3f& a, const vec3f& b, float t);
而实现代码也很简单,把声明了的函数实现三篇(三个函数体是一样的)
FastLerp(a,b,t)
{ return a+(b-a)*t; }
我想:( 这一点也不现代化!完全就是C语言!我们用的是C++!我们应该使用模板!)
当然,其实问题也大,如果将就下还可以使用。但是如果需要使用 int 类型或 unsigned int 类型的快速插值(FastLerp)呢?
typedef int s32;
typedef unsigned int u32; s32 flerp = (s32)FastLerp(, , 0.5);
此时,参数中的 0 和 1 都会转为浮点数,最后的返回类型是 float。但返回值有个向下取整的坑,可以再测试下:
void test_fast_lerp()
{
for(float i=; i<=; i+= 0.1f)
{
s32 flerp = FastLerp(, , i);
printf("%d\n", flerp);
}
}
只有 float i=1 时(忽略浮点的精度问题),flerp才为1,而正常应该是 i>=0.5f 时 flerp就为1。
为此为FastLerp实现了整型版本
s32 RoundToInt(float f); s32 FastLerp(const s32& a, const s32& b, float t)
{
return RoundToInt(FastLerp(float(a), float(b), t));
} u32 FastLerp(const u32& a, const u32& b, float t)
{ //WARN s32 to u32
return RoundToInt(FastLerp(float(a), float(b), t));
}
至此,问题基本解决了,但看着一堆同类型的函数,不写成模板真有一点对不起自己。
template<typename T, typename R>
inline void RoundLosePrecision(const R& r, T& t); template<>
inline void RoundLosePrecision(const float& r, s32& t)
{
t = RoundToInt(r);
} template<>
inline void RoundLosePrecision(const float& r, u32& t)
{ //WARN s32 to u32
t = RoundToInt(r);
} template<>
inline void RoundLosePrecision(const float& r, float& t)
{
t = r;
} template<>
inline void RoundLosePrecision(const vec2f& r, vec2f& t)
{
if(&r != &t){ t = r; }
} template<>
inline void RoundLosePrecision(const vec3f& r, vec3f& t)
{
if(&r != &t){ t = r; }
} template<typename T>
inline T FastLerp(const T& a, const T& b, float t)
{
T res;
RoundLosePrecision(a + (b-a) * t, res);
return res;
}
其中 RoundLosePrecision 函数处理类型转换时的四舍五入问题,如果是 float型转s32类型,则需要进行四舍五入操作;但如果都是float,则不需要四舍五入。
RoundLosePrecision 的最初版本是这样的:
template<typename T, typename R>
inline T RoundLosePrecision(R const & r);
但是模板不能通过左值类型生成对应的T类型,例如:
printf("%d", RoundLosePrecision(1.5f));
// c++ 11
auto a = RoundLosePrecision(1.3f);
虽然在调用时直接添加模板参数即可:
prinf("%d", RoundLosePrecision<s32, float>(1.5f));
auto a = RoundLosePrecision<u32, float>(1.3f);
但是在FastLerp里却行不通:
template<typename T>
inline T FastLerp(const T& a, const T& b, float t)
{
return RoundLosePrecision<T,???>(a + (b-a) * t);
}
因为你不知道(a+(b-a)*t) 的返回类型是什么,当然如果模板支持和boost的占位符就好办。
至此改造已经完成得差不多了,尝试编译更新……不改不知道,一改吓一跳!代码中又发现了新的问题:这样可以说是系统遗留问题。
因为当初提供的只有三个版本,所以在调用 float 时,可能会混合着 s32类型参数 和 float类型参数的FastLerp,在新添了 s32/u32 版本后编译器无法识别应该使用那个版本。而解决方法有两种:
//问题:
float alpha = xxxx;
s32 flerp = (s32)FastLerp(, alpha, t); //解决办法1
// (1)
s32 flerp = (s32)FastLerp(1.0f, alpha, t);
// (2)
s32 flerp = FastLerp(, (s32)alpha,t); //解决办法2:新增对应函数
float FastLerp(s32 a, float b, float t);
其中解决办法1(1)函数半个靠谱的解决办法,但这涉及到修改了其他人写的代码;而(2)就完全不靠谱了,因为你不知道alpha本身是否就需要小数点后的数据,强制类型转换活生生地把精度搞丢失了。
所以选择了解决方法2,而选择2也不好过,因为代码中存有大量的类似问题,所以要添加的函数有很多:
float FastLerp(s32 a, float b, float t);
float FastLerp(float a, s32 b, float t); float FastLerp(u32 a, float b, float t);
float FastLerp(float a, u32 b, float t); //全部转换成有符号类型
s32 FastLerp(s32 a, u32 b, float t);
s32 FastLerp(u32 a, s32 b, float t);
还好目前只用到u32/s32/float/vec2f/vec3f等类型,如需处理u64/s64/double等类型到时再添加便可(但也是很麻烦的事)。
如果我们细心点便发现FastLerp已经“变味”了,它不再是简单的类型T,而是包含了2种类型,所以函数应该是这样的:
template<typename T, typename R, typename S>
S FastLerp(const T& t, const R& r, float t);
这里的模板参数有3个,S可以通过隐式转换进行推导的(相关:http://msdn.microsoft.com/zh-cn/library/ms173105.aspx [C#]),例如:
int + float ->float
char + int -> int
而C++11标准的 decltype 就可以做到了:
template<typename T, typename R>
auto FastLerp(const T& t, const R& r, float t) -> decltype(t + r);
正因为有了 decltype,最初的RoundLosePrecision 也可以恢复到最初的形式,最终代码如下(注:代码未经过测试):
template<typename T, typename R>
inline T RoundLosePrecision(R const & r); template<typename T, typename R>
auto FastLerp(const T& a, const R& b, float t) -> decltype(a + b)
{
//因为目前手头上没有C++11的编译器,不知道如此复杂的类型编译器是否支持.
//如果不支持该怎么办?求教.(实在不想用 引用作为返回值的方法)
typedef decltye(a+(b-a)*t) type0;
typedef decltye(a+b) type1;
return RoundLosePrecision<type1, type0>(a + (b-a)*t);
}
到目前已经解决的七七八八,但还有一个坑没填,而且也不懂该怎么填,望各位赐教。
此问题是RoundLosePrecision模板化,上文只提供了几个常用类型的模板特化,但却没提供标准模板实现。
如果标明不提供标准模板实现,谁需要谁实现其特化版本。这样其实是将模版化之前的FastLerp复杂度转嫁到RoundLosePrecision,所以RoundLosePrecision也会造成“函数爆炸”,而且也不太符合“模板化”的初衷。况且,使用者不一定使用s32/u32/float等简单的类型,有可能参数使用匿名类型,例如:
template<typename T>
void xxx(T t); template<>
void xxx(int a)
{
printf("%d",a);
} enum
{
HAHA,
HEHE,
}; void main()
{
xxx(HEHE); //LNK1120: 1 个无法解析的外部命令
}
虽然在c++11也可以使用decltype解决这种问题,但是要写这么多真的只能是HEHE了。。。
template<>
void xxx(decltype(HEHE) h)
{
// TODO: HEHE
}
鉴于此,目前提供了一个能转换成int(s32)类型的版本。
template<typename T, typename R>
inline T RoundLosePrecision(const R& r)
{
return T(RoundLosePrecision<s32,R>(r));
}
但这样也产生了新的坑,例如double类型,或者各种包含隐式转换的类等等(例如half,自己实现的16bit的浮点型)。
所以,我们应该如何是好?请各位指教!
或者,应该用最初那样,不使用模板的方法才是正途。
又或者,RoundLosePrecision其实是我们想多了,直接忽略四舍五入就好?
再者,丫现在用的是vs2008,纯粹是显得蛋疼!(逃
template<typename T, typename R>
auto FastLerp(const T& a, const R& b, float t) -> decltype(a + b)
{
typedef decltye(a+b) type;
return type(a + (b-a)*t);
}
不支持C++11 decltype的噩耗的更多相关文章
- Code::Blocks如何支持C++11特性
为了给同事分享C++11标准,需要一个演示C++11的编程环境.VS2013太大,安装起来不太方便.由于电脑上之前有安装codeblock,于是升级MinGW.去MinGW官网http://www.m ...
- 转:linux下安装或升级GCC4.8,以支持C++11标准
转:http://www.cnblogs.com/lizhenghn/p/3550996.html C++11标准在2011年8月份获得一致通过,这是自1998年后C++语言第一次大修订,对C++语言 ...
- 让android项目支持boost 支持c++11
在Application.mk 里增加-D__GLIBC__ 让项目支持boost 增加 -std=c++11 让项目支持c++11 (3.x的cocos本身已经支持了的) 看起来这样: APP_S ...
- linux下安装或升级GCC4.8,以支持C++11标准
C++11标准在2011年8月份获得一致通过,这是自1998年后C++语言第一次大修订,对C++语言进行了改进和扩充.随后各编译器厂商都各自实现或部分实现了C++中的特性. 如需查看各编译器对C++1 ...
- Dev C++支持c++11标准的编译方法
一开始学C++的时候老师推荐的就是Dev C++这个IDE,用起来感觉还不错,使用起来比较简单,而且属于比较轻量级的,不怎么占用内存:缺点可能就是调试功能没有项VS那种大型IDE齐全和好用,不过对于一 ...
- 设置Eclipse支持C++ 11
设置Eclipse支持C++ 11 两个步骤: 项目 > Properties > C/C++ Build > Setting > GCC C++ Compiler > ...
- 使vim中Syntastic支持C++11
安装好Syntastic后发现不支持c++11,会提示错误incompatible with c++98,解决方法如下: .vimrc中加入: let g:syntastic_cpp_compiler ...
- windows完全支持C++11的轻量级编译器(官网MinGW和非官方的MinGW-builds)
作者:网事如风链接:https://www.zhihu.com/question/22923569/answer/23172337来源:知乎著作权归作者所有,转载请联系作者获得授权. 完全支持C++1 ...
- windows 下使clion支持c++11操作记录
最近用上了windows下的clion,发现默认安装的MINGW版本太低,导致所带的gcc版本竟然是3.5的,实在太老了,不支持c++11,于是手动修改了mingw的版本.首先去mingw的官网下载最 ...
随机推荐
- python基础知识5——赋值与深浅拷贝——整数和字符串,列表元组字典
深浅copy 一.数字和字符串 对于 数字 和 字符串 而言,赋值.浅拷贝和深拷贝无意义,因为其永远指向同一个内存地址. 1 import copy 2 # ######### 数字.字符串 #### ...
- php用simplexml来操作xml
<?php$username = 'zhansan';if (!file_exists('001.xml')){ $fp = fopen('001.xml', 'w'); $xmlContent ...
- VmWare为Fedora虚拟机扩展磁盘
1.根据步骤为虚拟机扩展磁盘空间. 查看步骤 2.进入Fedora系统 以下步骤为修改扇区,将扩展磁盘划分为分区 运行fdisk -l,命令执行结果的第一行显示了磁盘名称,同时可以看到当前磁盘分区情况 ...
- Java画图程序设计
本文讲述一个画图板应用程序的设计,屏幕抓图如下: 『IShape』 这是所有图形类(此后称作模型类)都应该实现接口,外部的控制类,比如画图板类就通过这个接口跟模型类“交流”.名字开头的I表示它是一个接 ...
- VC++ 中CDC与HDC的区别以及二者之间的转换
MFC类的前缀都是C开头的 H开头的大多数是句柄 这是为了助记,是编程读\写代码的好的习惯. CDC中所有MFC的DC的基类.常用的CClientDC dc(this);就是CDC的子类(或称派 ...
- Office 365系列六 ------ 创建sharepoint online网站
这节跟大家介绍简单的创建sharep online私有网站集,sharepoint online 可以给我们提供开箱即用的功能,比如文档库:可以给我们取代File Server,提供了版本管理,版本变 ...
- CCS3.3下执行优化
最近想研究一下CCS下的程序优化,之前也了解一些,现在查阅一下资料,整理一下. 当然优化有很多种,我本次先说的是执行优化方式. 首先打开我们自己的工程.工程文件若显示为黄色的,则表示此文件默认优化选项 ...
- javaSwing
一.使用java Swing写个登陆界面,感受一下布局管理器的特性和熟悉一下控件的使用 package com.swing; import java.awt.BorderLayout; import ...
- shell编程学习
1.项目中用到Linux的crontrab Linux下的定时执行主要是使用crontab文件中加入定制计划来执行,但是也不是非常复杂,基本上用过一遍就能记住了,关键是要记住/var/spool/cr ...
- 防御病毒邮件得看U-Mail邮件网关
其实在邮件通讯中,那些病毒.垃圾邮件.钓鱼软件也相当危险,在海量邮件中,你没法确定什么时间.哪一封会发起进攻,攻击的目标都有谁?但是一旦得逞,造成的损失又特别大. 最近美国同行又发现了一个新骗局:美国 ...