Шаблонная функция C ++ получает ошибочные значения по умолчанию
-
18-09-2020 - |
Вопрос
Я наткнулся на настоящего выжигателя мозгов в 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);
}
Теперь, когда я вызываю это с помощью defaults (transform(vector2<double>(0, 1), view_transform)
) Я не получаю ожидаемых значений.Вступая в transform
с помощью отладчика VC ++ я вижу z
и w
наличие "забавных" значений (что, по моему опыту, означает, что что-то не инициализировано должным образом).
Примером забавных значений могли бы быть:0,0078125000000000000 и 2,104431116947e-317#DEN
Теперь я попытался найти ответ на C ++ FAQ Lite, погуглив его;даже пытался успокоить себя Шубертом, но я ни за что на свете не смогу этого понять.Я предполагаю, что это действительно просто, и я подозреваю, что это какое-то шаблонное дурачество на работе.
Есть ли способ получить значения по умолчанию, которые я ожидаю и хочу, и почему он так поступает со мной?
Правка 1:
Если я вызову change, то вместо этого он использует floats (transform(vector2<float>(0, 1), view_transform)
) проблема исчезает.Похоже, это происходит только в том случае, если T
= double
.
Правка 2:
Это происходит только в том случае, если у меня есть две специализации для double
и float
.Если я использую специализацию с плавающей запятой в одном месте, двойная специализация получает странные значения по умолчанию.Если я изменю все места, где вызывается функция, так что она использует double, проблемы "исчезнут".Я все еще не понимаю, почему, хотя, похоже, что он использует ошибочные смещения или что-то в этом роде при настройке z
и w
.
Правка 3:
Истории из крипты C ++:
#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:
Сделал Проблема с подключением.
Решение
Следующее не выполняется для моей 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);
}
Выходной сигнал:
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
Реализация не должна неявно создавать экземпляр шаблона функции, шаблона-члена, невиртуальной функции-члена, класса-члена или члена со статическими данными шаблона класса, который не требует создания экземпляра.Не указано, создает ли реализация неявно экземпляр виртуальной функции-члена шаблона класса, если в противном случае экземпляр виртуальной функции-члена не был бы создан.Использование специализации шаблона в аргументе по умолчанию не должно приводить к неявному созданию экземпляра шаблона, за исключением того, что может быть создан экземпляр шаблона класса, где требуется его полный тип для определения корректности аргумента по умолчанию. Использование аргумента по умолчанию в вызове функции приводит к неявному созданию экземпляра специализации в аргументе по умолчанию.
Выделенная жирным шрифтом часть абзаца, как мне кажется, указывает на то, что каждая специализация, созданная из-за аргументов по умолчанию, будет неявно создана в единицу перевода при использовании.
Пункт 11:
Если шаблон функции f вызывается способом, требующим использования выражения аргумента по умолчанию, ищутся зависимые имена, проверяются семантические ограничения, и создание экземпляра любого шаблона, используемого в выражении аргумента по умолчанию, выполняется так, как если бы выражение аргумента по умолчанию было выражением, используемым в специализации шаблона функции с той же областью видимости, теми же параметрами шаблона и тем же доступом, что и у шаблона функции f, используемого в этот момент.Этот анализ называется созданием экземпляра аргумента по умолчанию.Созданный аргумент по умолчанию затем используется в качестве аргумента f .
Указывает, что даже если аргументами по умолчанию являются параметры шаблона, они будут созданы корректно.
Что ж, надеюсь, я правильно это истолковал.:-)
Другие советы
Оптимизирован ли код?Возможно, именно поэтому отладчик показывает вам неправильные значения.
Я попробовал этот более простой код (на 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";
}
Я не знаю, сработает ли это, но попробуйте использовать static_cast вместо приведения в стиле C для ваших значений по умолчанию.
*Редактировать: По-видимому, проблема в компиляторе.