zoukankan      html  css  js  c++  java
  • 不支持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, 1, 0.5);

    此时,参数中的 01 都会转为浮点数,最后的返回类型是 float。但返回值有个向下取整的坑,可以再测试下:

    void test_fast_lerp()
    {
        for(float i=0; i<=1; i+= 0.1f)
        {
            s32 flerp = FastLerp(0, 1, i);
            printf("%d
    ", flerp);
        }
    }

    只有 float i=1 时(忽略浮点的精度问题),flerp才为1,而正常应该是 i>=0.5fflerp就为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));
    }

    至此,问题基本解决了,但看着一堆同类型的函数,不写成模板真有一点对不起自己。

     1 template<typename T, typename R>
     2 inline void RoundLosePrecision(const R& r, T& t);
     3 
     4 template<>
     5 inline void RoundLosePrecision(const float& r, s32& t)
     6 {
     7     t = RoundToInt(r);
     8 }
     9 
    10 template<>
    11 inline void RoundLosePrecision(const float& r, u32& t)
    12 {   //WARN s32 to u32
    13     t = RoundToInt(r);
    14 }
    15 
    16 template<>
    17 inline void RoundLosePrecision(const float& r, float& t)
    18 {
    19     t = r;
    20 }
    21 
    22 template<>
    23 inline void RoundLosePrecision(const vec2f& r, vec2f& t)
    24 {
    25     if(&r != &t){ t = r; }
    26 }
    27 
    28 template<>
    29 inline void RoundLosePrecision(const vec3f& r, vec3f& t)
    30 {
    31     if(&r != &t){ t = r; }
    32 }
    33 
    34 template<typename T>
    35 inline T FastLerp(const T& a, const T& b, float t)
    36 {
    37     T res;
    38     RoundLosePrecision(a + (b-a) * t, res);
    39     return res;
    40 }

    其中 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(1, alpha, t);
    
    //解决办法1
    // (1)
    s32 flerp = (s32)FastLerp(1.0f, alpha, t);
    // (2)
    s32 flerp = FastLerp(1, (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);
    }
  • 相关阅读:
    24.最优布线问题(kruskal算法)
    24.最优布线问题(kruskal算法)
    Algs4-1.4.11为StaticSETofInts添加一个实列方法howMany()
    Algs4-1.4.9预测程序运行时间
    Algs4-1.4.10二分查找找出元素所在的最小索引
    Algs4-1.4.7统计算术运算与比较次数
    Algs4-1.4.8计算输入文件中相等的整数对的数量
    Algs4-1.4.6给出以下代码段的运行时间的增长数量级
    Algs4- 1.4.4参照表1.4.4为TwoSum建立一和类似的表格
    Algs4-1.4.2修改ThreeSum防止两个int值相加可能溢出
  • 原文地址:https://www.cnblogs.com/godzza/p/3345624.html
Copyright © 2011-2022 走看看