Скажем, у меня есть переменное количество аргументов, которые я хочу перемножить. Первый способ, о котором я думаю, - это рекурсивный алгоритм:

template<typename Head>
u64 Multiply(Head head) const
{
    return head;
}

template<typename Head, typename... Tail>
u64 Multiply(Head head, Tail... tail) const
{
    return head * Multiply(tail...);
}

Но потом я увидел такую уловку:

// u32 and u64 are 32 and 64 bit unsigned integers.
template<typename... T>
u64 Multiply(T... args)
{
    u64 res = 1;
    for (const u32& arg: {args...})
        res *= arg;
    return res;
}

Второй вариант мне кажется лучше. Легче читать. Однако как это влияет на производительность? Что-нибудь копируется? Что делает {args...} в первую очередь? Есть способ лучше?

У меня есть доступ к C ++ 14.

Отредактируйте, чтобы прояснить: речь идет об умножении времени выполнения, а не времени компиляции.

Для ясности: я не хочу обязательно вычислять целые числа (хотя это мое текущее приложение), но алгоритм, который я нашел, был специализирован для целых чисел.

Еще: аргументы одного типа. Алгоритмы без этого ограничения были бы очень интересны, но, возможно, для другого вопроса.

7
Aart Stuurman 27 Ноя 2016 в 21:59

2 ответа

Лучший ответ

Здесь задается несколько вопросов:

  1. Как это влияет на производительность? Не знаю. Вам нужно будет измерить. В зависимости от типа аргументов я могу представить, что компилятор полностью оптимизирует вещи в любом случае: он знает количество аргументов и типы.
  2. Что такое { args... }? Что ж, он создает std::initializer_list<T> для общего типа аргументов (при условии, что он есть). Однако вы можете использовать значение с std::common_type_t<T...> вместо фиксированного типа.
  3. Есть способ лучше? Есть несколько подходов, хотя я могу представить, что компилятор действительно неплохо справляется с этим расширением. Сразу приходит на ум альтернатива return (args * ... * 1);, которая, однако, требует C ++ 17. Если есть хотя бы один аргумент, * 1 можно не указывать: он нужен, чтобы избежать ошибки времени компиляции, если есть пустой список переменных с переменными параметрами.
4
Dietmar Kühl 27 Ноя 2016 в 19:35

Код

template<typename... T>
u64 Multiply(T... args)
{
    u64 res = 1;
    for (const u32& size : {args...})
        res *= size;
    return res;
}

Для меня немного загадочно :-) Почему у нас есть параметры шаблона с типом T и внутри метода мы использовали фиксированные значения размера? И имя переменной size выглядит очень непонятным, потому что эта переменная не имеет ничего общего ни с каким размером. И использование целочисленных типов внутри также не является допустимым предположением, если вы вводите данные с плавающей запятой в шаблон.

Хорошо, но чтобы ответить на ваш вопрос:

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

Обе версии могут быть сделаны constexpr, как я сейчас узнал :-), и они довольно хорошо подходят для расчета времени компиляции.

Чтобы ответить на вопрос из вашего комментария:

{args...}

Расширяется до:

{ 1,2,3,4}

Который представляет собой просто «массив» (std :: std :: initializer_list) и работает, только если все элементы имеют один и тот же тип.

Так что имея

for (const u32& size : {args...})

Просто перебирает массив.

1
Klaus 27 Ноя 2016 в 19:35