Давайте представим, что у меня есть несколько шаблонных функций, например:
template <int I> void f();
template <int I> void g();
template <int I> void h();
Как я могу вызвать последовательность любой из этих функций для последовательности параметров шаблона?
Другими словами, мне нужно такое поведение:
{some template magic}<1, 5>(f); // This is pseudocode, I don't need exactly this format of calling.
разворачивается в:
f<1>();
f<2>();
f<3>();
f<4>();
f<5>();
И мне нужен один и тот же метод для работы с каждой из моих функций (не только для f, но и для g и h) без написания большой неудобной структуры для каждой из этих функций.
Я могу использовать C ++ 11, и даже уже реализован в последней разработке GCC версии C ++ 1y / C ++ 14 функциональности (http://gcc.gnu.org/projects/cxx1y.html), например полиморфные лямбды.
С функциями C ++ 1y. Вместо прямого вызова функции и передачи аргумента шаблона в качестве аргумента шаблона, вы можете создать лямбду, которая принимает аргумент функции который содержит аргумент шаблона как часть его типа. То есть
f<42>();
[](std::integral_constant<int, 42> x) { f<x.value>(); }
[](auto x) { f<x.value>(); }
С этой идеей мы можем передать шаблон функции f
вокруг, когда завернут в такую полиморфную лямбду. Это возможно для любого типа перегрузки, одна из вещей, которую вы не можете сделать с обычными лямбдами.
Звонить f
с последовательностью шаблонных аргументов нам понадобятся общие классы индексов для трюка расширения индексов. Они будут в стандартной библиотеке C ++ 1y. Например, компилятор Coliru clang ++ по-прежнему использует более старую версию libstdc ++, которая не имеет AFAIK. Но мы можем написать свои собственные:
#include <utility>
using std::integral_constant;
using std::integer_sequence; // C++1y StdLib
using std::make_integer_sequence; // C++1y StdLib
// C++11 implementation of those two C++1y StdLib classes:
/*
template<class T, int...> struct integer_sequence {};
template<class T, int N, int... Is>
struct make_integer_sequence : make_integer_sequence<T, N-1, N-1, Is...> {};
template<class T, int... Is>
struct make_integer_sequence<T, 0, Is...> : integer_sequence<T, Is...> {};
*/
Когда мы пишем make_integer_sequence<int, 5>
мы получим тип, который является производным от integer_sequence<int, 0, 1, 2, 3, 4>
, Из последнего типа мы можем вывести индексы:
template<int... Indices> void example(integer_sequence<int, Indices...>);
Внутри этой функции у нас есть доступ к индексам в виде пакета параметров. Мы будем использовать индексы для вызова объекта lamba / function f
следующим образом (не шаблон функции f
из вопроса)
f( integral_constant<int, Indices>{} )...
// i.e.
f( integral_constant<int, 0>{} ),
f( integral_constant<int, 1>{} ),
f( integral_constant<int, 2>{} ),
// and so on
Пакеты параметров могут быть расширены только в определенных контекстах. Как правило, вы расширяете пакет как инициализаторы (например, фиктивный массив), так как их оценка гарантированно будет заказана (спасибо, Йоханнес Шауб). Вместо массива можно использовать тип класса, такой как
struct expand { constexpr expand(...) {} };
// usage:
expand { pattern... };
Фиктивный массив выглядит так:
int expand[] = { pattern... };
(void)expand; // silence compiler warning: `expand` not used
Еще одна сложная задача — иметь дело с функциями, возвращающими void в качестве шаблона. Если мы объединяем вызов функции с оператором запятой, мы всегда получаем результат
(f(argument), 0) // always has type int and value 0
Чтобы сломать любые существующие перегруженные операторы запятой, добавьте void()
(f(argument), void(), 0)
Наконец, объедините все вышесказанное, чтобы создать магию:
template<int beg, class F, int... Is>
constexpr void magic(F f, integer_sequence<int, Is...>)
{
int expand[] = { (f(integral_constant<int, beg+Is>{}), void(), 0)... };
(void)expand;
}
template<int beg, int end, class F>
constexpr auto magic(F f)
{
// v~~~~~~~v see below (*)
return magic<beg>(f, make_integer_sequence<int, end-beg+1>{});
}
Пример использования:
#include <iostream>
template<int N> void f() { std::cout << N << "\n"; }
int main()
{
//magic<1, 5>( [](auto x) { f<decltype(x)::value>(); } );
magic<1, 5>( [](auto x) { f<x.value>(); } );
}
(*) ПО МОЕМУ МНЕНИЮ end-beg+1
это плохая практика. Есть причина, по которой StdLib работает с полуоткрытыми диапазонами вида [begin, end)
: Пустой диапазон просто [begin, begin)
, С StdLib, использующим полуоткрытые диапазоны, может быть несовместимо использовать закрытые диапазоны здесь. (В StdLib есть одно исключение, о котором я знаю, это связано с PRNG и максимальным целочисленным значением.)
Я бы посоветовал вам создать свой magic
интерфейс для приема полуоткрытых диапазонов, т.е.
magic<1, 6>( [](auto x) { f<x.value>(); } ); // [1, 6) i.e. {1,2,3,4,5}
с реализацией
template<int beg, int end, class F>
constexpr auto magic(F f)
{
// v~~~~~v
return magic<beg>(f, make_integer_sequence<int, end-beg>{});
}
Обратите внимание на странное +1
исчезает.
Используя усовершенствованные функции и аргументы шаблона шаблона:
#include <iostream>
template<int I> class f {
public:
static void call() {
std::cout << I << '\n';
}
};
template<template<int I> class X, int I, int J> class magic {
public:
static void call() {
X<I>::call();
magic::call();
}
};
template<template<int I> class X, int I> class magic<X,I,I> {
public:
static void call() {
X<I>::call();
}
};
int main(int argc, char** argv) {
magic<f,2,6>::call();
return 0;
}