10

我在 C++ 中遇到了一个真正的大脑灼热者,它以前从未发生在我身上。

问题的要点是,在调用我的(模板)函数时,我定义的默认参数的值被打乱了。只有当我使用默认值调用函数时才会发生这种情况。

我的模板函数声明如下:

template <typename T>
vector2<T> transform(vector2<T> const &vec, matrix4<T> const &m, T z = T(0), T w = T(1));

稍后,在同一个标​​头中,定义如下:

template <typename T>
inline vector2<T> transform(vector2<T> const &vec, matrix4<T> const &m, T z, T w)
{
 vector4<T> res = m * vector4<T>(vec.x, vec.y, z, w);
 return vector2<T>(res.x, res.y);
}

现在,当我使用默认值 ( transform(vector2<double>(0, 1), view_transform)) 调用它时,我没有得到我期望的值。使用我看到transform的 VC++ 调试器zw具有“有趣”的值(根据我的经验,这意味着某些东西没有正确初始化)。

示例有趣的值是:0.0078125000000000000 和 2.104431116947e-317#DEN

现在我试着在 C++ FAQ Lite 上找到答案,用谷歌搜索它;甚至试图用舒伯特让自己平静下来,但我一辈子都想不通。我猜这真的很简单,我怀疑这是某种模板愚蠢的工作。

有没有办法获得我期望和想要的默认值,为什么它对我这样做?

编辑1:

如果我更改调用所以它使用浮点数而不是(transform(vector2<float>(0, 1), view_transform))问题就消失了。似乎只有在T=时才会发生这种情况double

编辑2:

只有当我对double和有两个专业时才会发生这种情况float。如果我在一个地方使用浮点特化,双重特化会得到奇怪的默认值。如果我更改调用该函数的所有位置,那么它会使用双倍的问题“消失”。我仍然不明白为什么,就像它在设置zand时使用了错误的偏移或其他东西一样w

编辑3:

来自 C++ Crypt 的故事:

#include <sgt/matrix4.hpp>

int main(int argc, char *argv[])
{
    sgt::matrix4<double> m0(
        2, 0, 0, 1,
        0, 2, 0, 1,
        0, 0, 1, 0,
        0, 0, 0, 1);

    m0 *= m0;

    sgt::vector2<double> blah0 = sgt::transform(sgt::vector2<double>(1, 0), m0);

    sgt::matrix4<float> m1(
        2, 0, 0, 1,
        0, 2, 0, 1,
        0, 0, 1, 0,
        0, 0, 0, 1);

    m1 *= m1;

    sgt::vector2<float> blah1 = sgt::transform(sgt::vector2<float>(1, 0), m1);

    printf("%f", blah0.x);
    printf("%f", blah1.x);
}

在 matrix4.hpp 中:

// ...

template <typename T>
vector2<T> transform(vector2<T> const &vec, matrix4<T> const &m, T z = T(0), T w = T(1));

template <typename T>
inline vector2<T> transform(vector2<T> const &vec, matrix4<T> const &m, T z, T w)
{
    vector4<T> res = m * vector4<T>(vec.x, vec.y, z, w);
    return vector2<T>(res.x, res.y);
}

// ...

如果我运行它,双重专业化的默认参数是正确的,但浮点版本将它的默认参数都设为零(0.000000),虽然更好,但它仍然不是z = 0w = 1.

编辑4:

提出了一个连接问题

4

3 回答 3

5

我在 Dev Studio 中的以下失败:

#include "stdafx.h"
#include <vector>
#include <iostream>

template <typename T>
std::vector<std::vector<T> > transform(std::vector<std::vector<T> > const &vec,
                                       std::vector<std::vector<std::vector<std::vector<T> > > > const &m,
                                       T z = T(0), T w = T(1));


template <typename T>
std::vector<std::vector<T> > transform(std::vector<std::vector<T> > const &vec,
                                       std::vector<std::vector<std::vector<std::vector<T> > > > const &m,
                                       T z, T w)
{
    std::cout << "Z" << z << "\n";
    std::cout << "W" << w << "\n";

    return vec;
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::vector<std::vector<int> >  xi;
    std::vector<std::vector<std::vector<std::vector<int> > > > mi;
    transform(xi,mi);

    std::vector<std::vector<float> >    xf;
    std::vector<std::vector<std::vector<std::vector<float> > > > mf;
    transform(xf,mf);

    std::vector<std::vector<double> >   xd;
    std::vector<std::vector<std::vector<std::vector<double> > > > md;
    transform(xd,md);
}

输出:

Z0
W1
Z0
W1.4013e-045
Z2.122e-314
W3.60689e-305

所以我想它没有按预期工作!!!

如果您删除预声明并将默认参数放在模板函数中,那么它会按预期工作。

#include "stdafx.h"
#include <vector>
#include <iostream>

template <typename T>
std::vector<std::vector<T> > transform(std::vector<std::vector<T> > const &vec,
                                       std::vector<std::vector<std::vector<std::vector<T> > > > const &m
                                       T z = T(0), T w = T(1))
{
    std::cout << "Z" << z << "\n";
    std::cout << "W" << w << "\n";

    return vec;
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::vector<std::vector<int> >  xi;
    std::vector<std::vector<std::vector<std::vector<int> > > > mi;
    transform(xi,mi);

    std::vector<std::vector<float> >    xf;
    std::vector<std::vector<std::vector<std::vector<float> > > > mf;
    transform(xf,mf);

    std::vector<std::vector<double> >   xd;
    std::vector<std::vector<std::vector<std::vector<double> > > > md;
    transform(xd,md);
}

这按预期工作。
这与模板预声明实际上不是函数预声明有关,因此它实际上没有默认参数,因此您在参数列表中获得随机值。

好的。不是根据我对标准的阅读,这应该按预期工作:

使用 n2521
第 14.7.1 节 隐式实例化
第 9 段

实现不应隐式实例化不需要实例化的类模板的函数模板、成员模板、非虚拟成员函数、成员类或静态数据成员。如果虚拟成员函数不会被实例化,则未指定实现是否隐式实例化类模板的虚拟成员函数。在默认参数中使用模板特化不应导致模板被隐式实例化,除非类模板可以在需要其完整类型以确定默认参数的正确性时被实例化。在函数调用中使用默认参数会导致默认参数中的特化被隐式实例化。

The bold part of the paragraph seems (to me) to indicate that each specialization created because of default arguments will be implicitly instantiated into the translation unit when used.

Paragraph 11:

If a function template f is called in a way that requires a default argument expression to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument expression is done as if the default argument expression had been an expression used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.

Indicates that even if the default arguments are template parameters they will be correctly instantiated.

Well I hope I interpreted that correctly. :-)

于 2010-07-21T00:23:21.957 回答
2

代码优化了吗?也许这就是调试器向您显示错误值的原因。

我尝试了这个更简单的代码(在 g++ 4.3.3 中),它按预期工作。

template <typename T>
T increment(T a, T b = T(1))
{
    return a + b;
}

int main()
{
    double a = 5.0;
    std::cout << increment(a) << ", ";
    std::cout << increment(a, 3.0) << "\n";
}
于 2010-07-21T00:06:12.537 回答
1

我不知道这是否可行,但请尝试使用 static_cast 而不是 C 样式转换作为默认值。

*编辑:显然,问题出在编译器上。

于 2010-07-21T00:01:12.857 回答