Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ template function gets erronous default values

I have hit upon a real brain scorcher in C++, it has never happened to me before.

The gist of the problem is that upon invocation of my (template) function the arguments I have defined defaults for have their values scrambled. It only happens if I call the function with the defaults.

My template function is declared like this:

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

It is later, in the same header, defined like this:

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);
}

Now when I call this with defaults (transform(vector2<double>(0, 1), view_transform)) I don't get the values I expect. Stepping into transform with VC++s debugger I see z and w having "funny" values (which in my experience means something isn't initialized properly).

Example funny values would be: 0.0078125000000000000 and 2.104431116947e-317#DEN

Now I've tried finding the answer on C++ FAQ Lite, googling it; even tried to calm myself with Schubert, but I can't for the life of me figure it out. I'm guessing it's really simple and I suspect it's some kind of template tomfoolery at work.

Is there a way to get the default values I expect and want, and why does it do this to me?

Edit 1:

If I the change call so it uses floats instead (transform(vector2<float>(0, 1), view_transform)) the problem goes away. It appears this only occurs if T = double.

Edit 2:

It only happens if I have two specializations for double and float. If I use a float specialization in one place the double specialization gets weird default values. If I change all the places the function is called so it uses double the problems "goes away". I still don't understand why though, it's like it's using faulty offsets or something when setting up z and w.

Edit 3:

Tales from the 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);
}

In 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);
}

// ...

If I run that, the double-specialization has it's default arguments correct, but the float version gets both it's default arguments as zero (0.000000) which albeit better, it's still is not z = 0 and w = 1.

Edit 4:

Made a Connect issue.

like image 560
Skurmedel Avatar asked Jul 20 '10 23:07

Skurmedel


People also ask

Can template parameters have default values?

Just like in case of the function arguments, template parameters can have their default values. All template parameters with a default value have to be declared at the end of the template parameter list.

Can default argument be used with the template class?

Can default arguments be used with the template class? Explanation: The template class can use default arguments.

Can C functions have default arguments?

C has no default parameters.

What are the disadvantages of templates in C++?

There are three primary drawbacks to the use of templates. First, many compilers historically have very poor support for templates, so the use of templates can make code somewhat less portable. Second, almost all compilers produce confusing, unhelpful error messages when errors are detected in template code.


3 Answers

The following fails for my in 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);
}

Output:

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

So I suppose it does not work as expected!!!

If you remove the pre-declaration and put the default arguments in the template function then it works as expected.

#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);
}

This works as expected.
This has something to do with the template pre-declaration not actually being a function pre-declaration and thus it does not actually have default parameters and as such you are getting random values in the parameter list.

OK. Not from my reading of the standard this should work as expected:

Using n2521
Section 14.7.1 Implicit instantiation
Paragraph 9

An implementation shall not implicitly instantiate a function template, a member template, a non-virtual member func- tion, a member class or a static data member of a class template that does not require instantiation. It is unspecified whether or not an implementation implicitly instantiates a virtual member function of a class template if the virtual member function would not otherwise be instantiated. The use of a template specialization in a default argument shall not cause the template to be implicitly instantiated except that a class template may be instantiated where its complete type is needed to determine the correctness of the default argument. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated.

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. :-)

like image 150
Martin York Avatar answered Sep 24 '22 14:09

Martin York


I do not know if this will work, but try using a static_cast instead of a C style cast for your default values.

*Edit: Apparently, the problem is the compiler.

like image 33
Alerty Avatar answered Sep 24 '22 14:09

Alerty


Is the code optimized? Maybe that's why the debugger is showing you the wrong values.

I tried this simpler code (in g++ 4.3.3) and it works as expected.

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";
}
like image 36
Matt Curtis Avatar answered Sep 21 '22 14:09

Matt Curtis