va_arg 与其他类型 return 0

va_arg with other types return 0

我正在尝试使用 模板 可变参数 的概念编写通用向量 class。我的 Vector class 声明及其属性的本质如下所示。

template<unsigned size, typename T>
struct Vector {
    T data[size];
};

例子

在下面提供的示例中,我创建了 2 个向量 3,一个是 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

Observation: When calling va_arg(va_list ap, type) with int as type the result is correct. But upon using other types such as float, 0 is just being returned.


集合的实现

Member method with variadic arguments.

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,不适用于 floatMore here

作为解决方法,您可以添加:

template<unsigned size, typename T>
struct Vector {
    T data[size];

    using Type = std::conditional_t<std::is_floating_point_v<T>,double,T>;

用于检查T是否为float,是的话,使用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 的固定版本:

Live demo

由于默认参数提升,您的代码有未定义的行为。

Variadic arguments: Default conversions:

When a variadic function is called, after lvalue-to-rvalue, array-to-pointer, and function-to-pointer conversions, each argument that is a part of the variable argument list undergoes additional conversions known as default argument promotions:

  • std::nullptr_t is converted to void*
  • float arguments are converted to double as in floating-point promotion
  • bool, char, short, and unscoped enumerations are converted to int or wider integer types as in integer promotion

你的编译器会警告你:

warning: passing an object that undergoes default argument promotion to 'va_start' has undefined behavior [-Wvarargs]

那么您的代码中发生的事情是,在 va_start(args, v);va_arg(args, T) 中您引用了 float,但可变参数部分是 double.