Функция шаблона С++ получает значения erronous по умолчанию

Я натолкнулся на реальный мозговой штурм на С++, это никогда не случалось со мной раньше.

Суть проблемы заключается в том, что при вызове моей (шаблонной) функции аргументы, которые я определил по умолчанию, имеют скремблированные значения. Это происходит только в том случае, если я вызываю функцию со значениями по умолчанию.

Моя функция шаблона объявляется следующим образом:

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 с отладчиком VС++, я вижу z и w имеющие "смешные" значения (что, по моему опыту, означает, что что-то не инициализировано должным образом).

Примеры смежных значений: 0.0078125000000000000 и 2.104431116947e-317 # DEN

Теперь я пробовал найти ответ на С++ FAQ Lite, googling it; даже пытался успокоиться с Шубертом, но я не могу, чтобы жизнь меня поняла. Я предполагаю, что это очень просто, и я подозреваю, что на работе работает какой-то шаблонный дурачок.

Есть ли способ получить значения по умолчанию, которые я ожидаю и хочу, и почему он делает это для меня?

Изменить 1:

Если я вызываю изменение, поэтому вместо него использует float (transform(vector2<float>(0, 1), view_transform)), проблема исчезает. Похоже, это происходит только в том случае, если T= double.

Изменить 2:

Это происходит только в том случае, если у меня есть две специализации для double и float. Если я использую специализацию float в одном месте, двойная специализация получает странные значения по умолчанию. Если я изменю все места, вызываемые функцией, поэтому он использует двойные проблемы "уходит". Я все еще не понимаю, почему, хотя, как будто он использует ошибочные смещения или что-то при настройке z и w.

Изменить 3:

Рассказы из Crypt 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 = 0 и w = 1.

Изменить 4:

Сделано Соединить вопрос.

Ответ 1

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

Это работает так, как ожидалось.
Это связано с тем, что предварительная декларация шаблона не является предварительным объявлением функции и, следовательно, на самом деле не имеет параметров по умолчанию, и поэтому вы получаете случайные значения в списке параметров.

OK. Не из моего чтения стандарта это должно работать как ожидалось:

Использование n2521
Раздел 14.7.1 Неявная реализация Пункт 9

Реализация не должна имплицитно создавать шаблон функции, шаблон-член, не виртуальную функцию-член, класс-член или статический элемент данных шаблона класса, который не требует создания экземпляра. Неясно, реализует ли реализация неявно экземпляр виртуальной функции-члена шаблона класса, если бы функция виртуального члена не была бы реализована иначе. Использование специализации шаблона в аргументе по умолчанию не должно приводить к тому, что шаблон неявно создается, за исключением того, что шаблон шаблона может быть создан, когда его полный тип необходим для определения правильности аргумента по умолчанию. Использование аргумента по умолчанию в вызове функции приводит к тому, что специализации в аргументе по умолчанию неявно создаются.

Полужирная часть параграфа кажется мне (мне), чтобы указать, что каждая специализация, созданная из-за аргументов по умолчанию, будет неявно создаваться в блоке перевода при использовании.

Пункт 11:

Если шаблон функции f вызывается таким образом, что требуется использовать выражение аргументов по умолчанию, зависимые имена просматриваются, проверяются ограничения семантики и выполняется создание экземпляра любого шаблона, используемого в выражении аргумента по умолчанию как будто выражение аргумента по умолчанию было выражением, используемым в специализации шаблона функции с той же областью, теми же параметрами шаблона и тем же доступом, что и шаблон функции f, используемый в этой точке. Этот анализ называется созданием аргументов по умолчанию. Конкретный аргумент по умолчанию затем используется как аргумент f.

Указывает, что даже если аргументы по умолчанию являются параметрами шаблона, они будут правильно созданы.

Надеюсь, я правильно это понял.: -)

Ответ 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";
}

Ответ 3

Я не знаю, будет ли это работать, но попробуйте использовать static_cast, а не для стиля C для ваших значений по умолчанию.

* Изменить: По-видимому, проблема заключается в компиляторе.