Как получить параметры шаблона переменной формы без шаблона вспомогательной функции? - PullRequest
15 голосов
/ 02 мая 2019

Предположим, у меня есть

template<int ...>
struct Ints { };

class MyClass
{
public:
    Ints<1, 2, 3> get() { return Ints<1, 2, 3>(); }
};

То, что я хочу сделать, просто.

template <class T>
vector<int> MyFunc1(T& x)
{
    Ints<S...> result = x.get();
    vector<int> t = { S... };
    return t;
}

Примерно так.(Здесь MyClass может быть одним из примеров T.) По-видимому, для компилятора S... кажется недействительным.

template <class T, int... S>
vector<int> MyFunc2(T& x)
{
    Ints<S...> result = x.get();
    vector<int> t = { S... };
    return t;
}

Это тоже не работает.Я думаю, что с get() S... становится конкретным и автоматически выводимым, но компилятор не распознает его.(Я не уверен, но C ++ не выводит параметры шаблона, смотрящие внутри функции, а только аргументы и возвращаемый тип)* was.

template <int ...S>
vector<int> temp(Ints<S...> not_used)
{
    return { S... };
}

template <class T>
vector<int> MyFunc3(T& x)
{
    auto result = x.get();
    return temp(result);
}

Работает хорошо, но требует еще одной дополнительной вспомогательной функции, которая ничего не делает, а просто обеспечивает синтаксически понятный способ сопоставления S... с использованием шаблонов.

Я действительно хочусделать это только в одной функции .Действительно ли мне нужно определять вспомогательную функцию всякий раз, когда я хочу получать пакеты параметров, каждый раз?

Edit: Ints и MyFunc - просто игрушечный пример.Я хочу знать общий метод получения параметров шаблона!

Ответы [ 4 ]

7 голосов
/ 02 мая 2019

Как бы выглядел идеальный интерфейс?

Если бы мы дали переменную типа Ints<S...>, мы в идеале могли бы использовать S... с минимально возможной модификацией.

В этом случае мы можем спроектировать интерфейс, который позволит нам использовать пакет параметров в качестве входных данных для функции с переменным числом или лямбда-выражения, даже если использовать значения в качестве параметров шаблона.

Предлагаемый интерфейс [Динамический регистр / целые числа, передаваемые в качестве значений]

И статический регистр, и динамический регистр имеют схожие интерфейсы, однако динамический регистр немного чище и способствует лучшему представлению.Учитывая переменную и функцию, мы применяем функцию с пакетом параметров, содержащимся в определении переменной.

Ints<1, 2, 3> ints;

// Get a vector from ints
// vec = {1, 2, 3}
auto vec = ints | [](auto... S) { return std::vector {S...}; };

// Get an array from ints
// arr = {1, 2, 3}
auto arr = ints | [](auto... S) { return std::array {S...}; }; 

// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = ints | [](auto... S) { return std::make_tuple(S...); };

// Get sum of ints using a fold expression
auto sum = ints | [](auto... S) { return (S + ...); }; 

Это простой унифицированный синтаксис, который позволяет нам взять S и использовать его в качестве пакета параметров.

Написание этого интерфейса

Эта часть также довольно проста.Мы берем переменную типа Ints<S...> и функцию и применяем функцию с S....

template<int... S, class Func>
auto operator|(Ints<S...>, Func&& f) {
    return f(S...); 
}

Предлагаемый интерфейс [Статический регистр / целые числа, используемые в качестве параметров шаблона]

Как было сказано ранее, статический регистр имеет интерфейс, аналогичный динамическому регистру, и не будетбольшая часть концептуально.С точки зрения пользователя, единственное отличие состоит в том, что вместо использования S... в качестве пакета параметров мы используем ll use S.value ... `в качестве пакета.

Для каждого значения мы хотим инкапсулировать его в соответствующий тип, связанный с этим значением.Это позволяет нам получить к нему доступ в контексте constexpr.

template<int Value>
struct ConstInt {
    constexpr static int value = Value;
};

Чтобы отличить его от динамического регистра, я собираюсь перегрузить / вместо |.В противном случае они ведут себя аналогично.Реализация почти такая же, как и в динамическом случае, за исключением того, что значения заключены в класс ConstInt, и у каждого будет свой собственный тип.

template<int... S, class F>
auto operator/(Ints<S...>, F&& func) {
    return func(ConstInt<S>()...); 
}

Статическое использование этого интерфейса

C ++ позволяет нам получать доступ к статическим членам класса, используя тот же синтаксис, что и нестатические члены, без потери статуса constexpr.

Допустим, у меня есть ConstInt со значением 10. Я могу напрямую использовать I.value в качестве параметра шаблона, или я могу использовать decltype(I)::value:

// This is what'll be passed in as a parameter
ConstInt<10> I;

std::array<int, I.value> arr1;
std::array<int, decltype(I)::value> arr2; 
// Both have length 10

Расширение пакета параметровследовательно, необычайно прямолинейный, и в итоге он почти идентичен динамическому случаю, единственное отличие состоит в том, что .value добавляется к S.Ниже показаны примеры из динамического регистра, на этот раз с использованием синтаксиса статического регистра:

Ints<1, 2, 3> ints;

// Get a vector from ints
auto vec = ints | [](auto... S) { return std::vector {S.value...}; };

// Get an array from ints
// arr = {1, 2, 3}
auto arr = ints | [](auto... S) { return std::array {S.value...}; }; 

// Get a tuple from ints
auto tup = ints | [](auto... S) { return std::make_tuple(S.value...); };

// Get sum of ints using a fold expression
auto sum = ints | [](auto... S) { return (S.value + ...); }; 

Так что же нового? Поскольку value является constexpr, S.value можно использовать тривиально в качестве параметра шаблона. В этом примере мы используем S.value для индексации в кортеж с помощью std::get:

auto tupA = std::make_tuple(10.0, "Hello", 3); 

auto indicies = Ints<2, 0, 1>{};

// tupB = {3, 10.0, "Hello"}
auto tupB = indicies / [&](auto... S) { 
    return std::make_tuple(std::get<S.value>(tupA)...);
};

И в этом примере мы возводим в квадрат каждый элемент в последовательности и возвращаем новую последовательность:

auto ints = Ints<0, 1, 2, 3, 4, 5>(); 

// ints_squared = Ints<0, 1, 4, 9, 16, 25>(); 
auto ints_squared = ints / [](auto... S) {
    return Ints<(S.value * S.value)...>(); 
};

Альтернативное решение, позволяющее избежать перегрузки операторов

Если вы хотите избежать перегрузки операторовМы можем черпать вдохновение из функционального программирования и обрабатывать вещи с помощью функции unpack, написанной примерно так:

template<int... vals>
auto unpack(Ints<vals...>) {
    return [](auto&& f) { return f(vals...); }; 
}

// Static case
template<int... vals>
auto unpack_static(Ints<vals...>) {
    return [](auto&& f) { return f(ConstInt<vals>()...); }; 
}

Так что же такое unpack? Эта функция занимает кучузначений и возвращает функцию, которая принимает другую функцию и применяет функцию с значениями в качестве входных данных.

Функция unpack позволяет нам применять эти значения к другой функции в качестве параметров.

Мы можем присвоить результат переменной с именем apply_ints, а затем мы можем использовать apply_ints, чтобы обработать все конкретные варианты использования:

Ints<1, 2, 3> ints; //this variable has our ints

auto apply_ints = unpack(ints); // We use this function to unpack them

Мы можем переписать примеры ранее, на этот раз используя apply_ints:

// Get a vector from ints
// vec = {1, 2, 3}
auto vec = apply_ints([](auto... S) { return std::vector {S...}; });

// Get an array from ints
// arr = {1, 2, 3}
auto arr = apply_ints([](auto... S) { return std::array {S...}; }); 

// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = apply_ints([](auto... S) { return std::make_tuple(S...); });

// Get sum of ints using a fold expression
auto sum = apply_ints([](auto... S) { return (S + ...); }); 

Приложение

В этом приложении приводится краткий обзор, показывающий, как использовать этот синтаксис более широко (например, при работе с несколькими отдельными пакетами параметров).

Бонусный пример: объединение значений из двух отдельных пакетов

Чтобы дать вам лучшее представление о гибкости этого интерфейса, вот пример, в котором мы используем его для объединения значений из двух отдельных пакетов.

Ints<1, 2, 3> intsA;
Ints<10, 20, 30> intsB;

// pairs = {{1, 10}, {2, 20}, {3, 30}}
auto pairs = intsA | [&](auto... S1) {
    return intsB | [&](auto... S2) {
        return std::vector{ std::pair{S1, S2}... }; 
    };
};

NB: MSVC и GCC компилируют этот пример без проблем, однако Clang его затягивает.Я предполагаю, что MSVC и GCC верны, но я точно не знаю.

Бонусный пример: получение двумерной таблицы времени

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

В этом случае я использую его для создания таблицы времени.

Ints<1, 2, 3, 4, 5, 6, 7, 8, 9> digits;

auto multiply = [](auto mul, auto... vals) {
    return std::vector{(mul * vals)...}; 
};

auto times_table = digits | [&](auto... S1) {
    return digits | [&](auto... S2) {
        return std::vector{ multiply(S1, S2...)... };
    };
};
6 голосов
/ 02 мая 2019

В C ++ 2a вы можете использовать шаблонную лямбду для определения вашего помощника внутри вашей функции, например:

auto v = []<std::size_t...Is>(std::index_sequence<Is...>){return std::vector{Is...};}(seq);
//         ^^^^^^^^^^^^^^^^^^ New in C++2a

Демо

1 голос
/ 02 мая 2019

Если вы не используете / не создаете вспомогательный шаблон, вам нужно другим способом предоставить значения.

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

template<int ...ints>
struct Ints {
  constexpr static std::initializer_list<int> vals = {ints...};
};

Так как это constexpr, оно должно быть оценено во время компиляции и не должно вызывать затрат времени выполнения. Теперь вы сможете сделать что-то вроде:

return std::vector<int>(ints.vals);
0 голосов
/ 02 мая 2019

Я бы предложил добавить функции к struct Int, чтобы получить различные представления

#include <vector>
#include <array>

template<int ...values>
struct Ints {
    auto getAsVector() const {
        return std::vector<int>({ values... });
    }

    constexpr auto getAsArray() const {
        return std::array<int, sizeof...(values)>({ values... });
    }
};

class MyClass
{
public:
    Ints<1, 2, 3> get() { return Ints<1, 2, 3>(); }
};

int main() {
    MyClass a;
    auto array = a.get().getAsVector();
    return array.size();

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...