Динамическая диспетчеризация функций шаблона?

137
14

Можно ли во время выполнения выбрать функцию шаблона?
Что-то вроде:


template<int I>
struct A {
static void foo() {/*...*/}
};

void bar(int i) {
A<i>::f(); // <-- ???
}

спросил(а) 2021-01-19T16:06:52+03:00 2 месяца, 3 недели назад
1
Решение
157

Типичный "трюк", чтобы скомпилировать время компиляции и время выполнения при работе с шаблонами, - это посещение типа варианта. Это, например, то, что представляет собой библиотека общего изображения (доступная как Boost.GIL или автономная). Обычно он принимает форму:


typedef boost::variant<T, U, V> variant_type;
variant_type variant = /* type is picked at runtime */
boost::apply_visitor(visitor(), variant);

где visitor - полиморфный функтор, который просто переходит к шаблону:


struct visitor: boost::static_visitor<> {
template<typename T>
void
operator()(T const& t) const
{ foo(t); } // the real work is in template<typename T> void foo(T const&);
};

Это имеет красивый дизайн, что список типов, которые шаблон будет/может быть создан с помощью (здесь, синоним типа variant_type), не связан с остальной частью кода. Метафунции, такие как boost::make_variant_over, также позволяют вычислять список используемых типов.


Поскольку этот метод недоступен для параметров не-типа, вам нужно "развернуть" сеанс вручную, что, к сожалению, означает, что код не является читаемым/поддерживаемым.


void
bar(int i) {
switch(i) {
case 0: A<0>::f(); break;
case 1: A<1>::f(); break;
case 2: A<2>::f(); break;

default:
// handle
}
}


Обычный способ справиться с повторением в указанном выше переключателе состоит в том, чтобы (ab) использовать препроцессор. Пример (непроверенный) с использованием Boost.Preprocessor:

#ifndef LIMIT
#define LIMIT 20 // 'reasonable' default if nothing is supplied at build time
#endif
#define PASTE(rep, n, _) case n: A< n >::f(); break;

void
bar(int i) {
switch(i) {
BOOST_PP_REPEAT(LIMIT, PASTE, _)

default:
// handle
}
}

#undef PASTE
#undef LIMIT


Лучше найти хорошие самозаверяющие имена для LIMIT (не повредит для PASTE) и ограничить приведенное выше генерирование кода только одним сайтом.


Здание из решения David и ваши комментарии:


template<int... Indices>
struct indices {
typedef indices<Indices..., sizeof...(Indices)> next;
};

template<int N>
struct build_indices {
typedef typename build_indices<N - 1>::type::next type;
};

template<>
struct build_indices<0> {
typedef indices<> type;
};

template<int... Indices>
void
bar(int i, indices<Indices...>)
{
static void (*lookup[])() = { &A<Indices>::f... };
lookup[i]();
}


то для вызова bar: bar(i, typename build_indices<N>::type()), где N будет вашей постоянной константой времени, sizeof...(something). Вы можете добавить слой, чтобы скрыть "уродство" этого вызова:


template<int N>
void
bar(int i)
{ bar(i, typename build_indices<N>::type()); }

который называется bar<N>(i).

ответил(а) 2021-01-19T16:06:52+03:00 2 месяца, 3 недели назад
130

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


void bar( int i ) {
static void (*lookup[])(void) = { &A<0>::foo, &A<1>::foo, &A<2>::foo, &A<3>::foo };
lookup[i]();
}

Конечно, я выбрал самый простой вариант для примера. Если требуемое число не является последовательным или нулевым, вы можете предпочесть std::map<int, void (*)(void) >, а не массив. Если количество различных вариантов, которые вы хотите использовать, больше, вы можете добавить код, чтобы автоматически вводить в действие шаблоны, а не вручную вводить их все... Но вам нужно было бы подумать, что каждое создание шаблона создает новый функции, и вы можете проверить, действительно ли вам это нужно.

EDIT: я написал post, реализующий ту же инициализацию, используя только функции С++ 03, это казалось слишком длинным для ответа.


Люк Дантон написал интересный ответ здесь, который включает в себя, помимо прочего, инициализацию таблицы поиска с использованием конструкций С++ 0x. Мне не совсем нравится, что это изменение интерфейса требует дополнительного аргумента, но это легко решить через промежуточного диспетчера.

ответил(а) 2021-01-19T16:06:52+03:00 2 месяца, 3 недели назад
96

Нет, шаблоны являются функцией времени компиляции, а i не известно во время компиляции, поэтому это невозможно. A<I>::foo() следует адаптировать к чему-то вроде A::foo(i).

ответил(а) 2021-01-19T16:06:52+03:00 2 месяца, 3 недели назад
62

Аргумент шаблона должен быть известен во время компиляции. Таким образом, компилятор не может пройти A<i>::foo().


Если вы хотите работать, тогда вы должны сделать bar() также template:


template<int i>
void bar() {
A<i>::f(); // ok
}

Для этого вы должны знать аргумент bar() во время компиляции.

ответил(а) 2021-01-19T16:06:52+03:00 2 месяца, 3 недели назад
61

NO

Шаблоны реализуют полиморфизм времени компиляции, а не полиморфизм времени выполнения.

ответил(а) 2021-01-19T16:06:52+03:00 2 месяца, 3 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема