Подтвердить что ты не робот

С++ вариационный шаблон с удвоением

Следующий код

#include <initializer_list>
#include <vector>

template<int ...>
const std::vector<int>*make_from_ints(int args...)
{ return new std::vector<int>(std::initializer_list<int>{args}); }

компилирует (с GCC 6.3, на Debian/Sid/x86-64) правильно, и я ожидаю его для вызова типа

auto vec = make_from_ints(1,2,3);

чтобы вернуть указатель на некоторый вектор целых чисел, содержащий 1, 2, 3.

Однако, если я заменил int на double, то есть, если я добавлю следующее (в том же файле basiletemplates.cc...):

template<double ...>
const std::vector<double>*make_from_doubles(double args...)
{ return new std::vector<double>(std::initializer_list<double>{args}); }

Я получаю ошибку компиляции:

basiletemplates.cc:8:17: error: ‘double’ is not a valid type
                for a template non-type parameter
 template<double ...>
                 ^~~

и я не понимаю, почему. Ведь оба int и double - это скалярные числовые типы POD (предварительно определенные в стандарте С++ 11).

Как получить вариационную функцию шаблона, чтобы иметь возможность кодировать:

auto dvec = make_from_doubles(-1.0, 2.0, 4.0);

и получить указатель на некоторый вектор удвоений, содержащий -1.0, 2.0, 4.0?

BTW, компиляция для С++ 14 (с g++ -Wall -std=c++14 -c basiletemplates.cc) и использование clang++ (версия 3.8.1) вместо g++ ничего не менять.

4b9b3361

Ответ 1

template<int ...>
const std::vector<int>*make_from_ints(int args...)
{ return new std::vector<int>(std::initializer_list<int>{args}); }

В приведенном выше фрагменте есть множество проблем:

  • Возврат const std::vector<int>* вместо std::vector<int> и без необходимости использования динамического выделения.

    • Даже если вы хотите использовать динамическое распределение, вы должны использовать std::make_unique вместо new.
  • Вы определили make_from_ints как функцию шаблона, которая принимает любое количество параметров шаблона int, но вы не даете этим int имя - вы никогда не сможете их использовать!

  • Ваша подпись фактически анализируется как make_from_ints(int args, ...) - это подпись C va_args, которая не имеет никакого отношения к вариационным шаблонам.

    • Правильный синтаксис для пакета аргументов - type... name.

Если вы хотите принять любое количество аргументов определенного типа, которое хорошо работает с выводом аргумента шаблона, самый простой способ - использовать обычный вариационный шаблон, который принимает произвольное количество типов и static_assert их тип (или использует std::enable_if для удобства SFINAE). Вот пример:

template <typename... Ts>
auto make_from_ints(Ts... xs) 
{ 
    static_assert((std::is_same<Ts, int>::value && ...));
    return std::vector<int>{xs...};
}

template <typename... Ts>
auto make_from_doubles(Ts... xs) 
{ 
    static_assert((std::is_same<Ts, double>::value && ...));
    return std::vector<double>{xs...};
}

Использование:

for(auto x : make_from_ints(1,2,3,4)) std::cout << x << " ";
std::cout << "\n";
for(auto x : make_from_doubles(1.0,1.5,2.0,2.5)) std::cout << x << " ";

1 2 3 4

1 1,5 2 2,5

живой пример в wandbox


Обратите внимание, что я использую С++ 17-кратное выражение, чтобы проверить, являются ли все Ts... здесь определенного типа:

static_assert((std::is_same<Ts, int>::value && ...));

Если у вас нет доступа к функциям С++ 17, это можно легко заменить чем-то вроде:

template <typename... Ts>
constexpr auto all_true(Ts... xs)
{
    for(auto x : std::initializer_list<bool>{xs...}) 
        if(!x) return false;

    return true;
}

// ...

static_assert(all_true(std::is_same<Ts, int>{}...));