va_arg с другими типами возвращает 0 - PullRequest
1 голос
/ 10 марта 2020

Я пытаюсь написать обобщенный c векторный класс, используя понятия шаблонов и variadi c аргументов . Суть моего объявления класса Vector и его атрибутов выглядит следующим образом.

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';

Outuput

1,0,2
1,0,0

Ожидаемый выход

1,0,2
1,0,2

Наблюдение: При вызове va_arg(va_list ap, type) с int как type результат верный. Но при использовании других типов, таких как float, просто возвращается 0.


Реализация метода Set

Member с аргументами variadi c.

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

Замечания

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

Ответы [ 2 ]

2 голосов
/ 10 марта 2020

У вас неопределенное поведение, 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 ++ вы должны использовать шаблон функции variadi c:

template<class ... Args>
void Set2(Args... args)
{
    int idx = 0;
    int fakeArray[] = { (data[idx++] = args,0)... };
    static_cast<void>(fakeArray);
}

Исправлена ​​версия с static_assert на размер пакета параметров:

Live демо

1 голос
/ 10 марта 2020

Ваш код имеет неопределенное поведение из-за продвижения аргументов по умолчанию .

Variadi c аргументов: преобразования по умолчанию :

Когда вызывается функция variadi c, после преобразований lvalue-to-rvalue, array-to-pointer и function-to-pointer каждый аргумент, являющийся частью списка переменных аргументов, подвергается дополнительным преобразованиям, известным как продвижения аргументов по умолчанию :

  • std::nullptr_t преобразуется в void*
  • float аргументы преобразуются в double как при повышении с плавающей запятой
  • bool , char, short и перечисления с незаданной областью преобразуются в целые типы int или более широкие, как при целочисленном продвижении

И ваш компилятор предупредит вас об этом:

предупреждение: передача объекта, который подвергается расширению аргумента по умолчанию, в 'va_start' имеет неопределенное поведение [-Wvarargs]

То, что происходит в вашем коде, происходит в va_start(args, v); и va_arg(args, T) вы ссылаетесь на float, но часть variadi c равна double.

...