我正试图使用模板和可变参数的概念来编写一个通用向量类。Vector类声明的本质及其属性如下所示。
template<unsigned size, typename T>
struct Vector {
T data[size];
};
示例
在下面提供的示例中,我创建了两个向量3s,一个是类型int
,另一个是float
。然后,我调用一个Set函数(下一节中的实现(,传入相同的参数,但类型不同。int
类型的向量的结果是正确的,但float
类型的向量结果不是我所期望的。
Math::Vector<3, int> i_vec3;
i_vec3.Set(1, 0, 2);
std::cout << i_vec3 << 'n';
Math::Vector<3, float> f_vec3;
f_vec3.Set(1.0f, 0.0f, 2.0f);
std::cout << f_vec3 << 'n';
输出
1,0,2
1,0,0
预期输出
1,0,2
1,0,2
观察:当用
int
作为type
调用va_arg(va_list ap, type)
时,结果是正确的。但是在使用其他类型(如float
(时,只返回0。
集合的实现
具有可变参数的成员方法。
void Set(T v, ...) {
va_list args;
va_start(args, v);
data[0] = v;
for (unsigned i = 1; i < size; ++i)
data[i] = va_arg(args, T);
va_end(args);
}
再现性示例
这是一个用于测试的问题的最小版本。
#include <iostream>
#include <cstdarg>
namespace Math {
template<unsigned size, typename T>
struct Vector {
T data[size];
void Set(T v, ...) {
va_list args;
va_start(args, v);
data[0] = v;
for (unsigned i = 1; i < size; ++i)
data[i] = va_arg(args, T);
va_end(args);
}
};
template<unsigned size, typename T>
std::ostream& operator<<(std::ostream& os, const Vector<size, T>& v) {
os << v.data[0];
for (unsigned i = 1; i < size; ++i)
os << ',' << v.data[i];
return os;
}
}
int main() {
Math::Vector<3, int> i_vec3;
i_vec3.Set(1, 0, 2);
std::cout << i_vec3 << 'n';
Math::Vector<3, float> f_vec3;
f_vec3.Set(1.0f, 0.0f, 2.0f);
std::cout << f_vec3 << 'n';
return 0;
}
备注
这是我第一次尝试各种各样的论点,所以很高兴知道我可能有什么误解导致了这个意想不到的结果。
您有未定义的行为,浮点的va_arg
只能与double
一起使用,而不能与float
一起使用。点击此处了解更多
作为变通方法,您可以添加:
template<unsigned size, typename T>
struct Vector {
T data[size];
using Type = std::conditional_t<std::is_floating_point_v<T>,double,T>;
为了检查T
是否为浮点,如果是,请使用double。呼叫是:
data[i] = va_arg(args, Type);
,...
在C中很好,在C++中应该使用可变函数模板:
template<class ... Args>
void Set2(Args... args)
{
int idx = 0;
int fakeArray[] = { (data[idx++] = args,0)... };
static_cast<void>(fakeArray);
}
在参数包大小上使用static_assert的固定版本:
现场演示
由于默认参数提升,您的代码具有未定义的行为。
可变参数:默认转换:
当调用变元函数时,在从左值到右值、从数组到指针以及从函数到指针的转换之后,作为变量参数列表一部分的每个参数都会进行额外的转换,称为默认参数提升:
std::nullptr_t
转换为void*
float
变元转换为double
,如同浮点提升一样bool
、char
、short
和无范围枚举被转换为int或更宽的整数类型,如整数提升中所示
您的编译器会警告您这一点:
警告:将经过默认参数提升的对象传递到"va_start"具有未定义的行为[-Wvarargs]
因此,代码中发生的情况是,在va_start(args, v);
和va_arg(args, T)
中,您引用的是float
,但可变部分是double
。