Передача переменной в качестве аргумента шаблона

Я работаю с библиотекой, которая предоставляет интерфейс для работы. Одна из функций этой библиотеки такова:

template <int a>
void modify(){}

Мне нужно изменить параметры от 1 до 10, т.е. вызвать modify с аргументами шаблона от 1 до 10. Для этого я написал этот код (базовая версия кода, фактический код намного больше).

for(int i=0; i<10; i++){
    modify<i>();
}

При компиляции я получаю следующую ошибку

error: 'i' cannot appear in constant-expression

После прохождения некоторых ссылок в Интернете я узнал, что не могу передать какое-либо значение в качестве аргумента шаблона, который не оценивается во время компиляции. Мой вопрос таков: 1. Почему компилятор не может оценить i во время компиляции? 2. Есть ли какой-либо другой способ достичь цели, которую я пытаюсь достичь, не меняя интерфейс API?


Есть еще одна вещь, которую я хочу сделать. Вызовите изменение в качестве модификации, где VAR является результатом некоторого функционального вычисления. Как я могу это сделать?

Ответ 1

Каково значение я (которое не является константой) во время компиляции? Нет способа ответить, если не выполнить цикл. Но выполнение не является "компиляцией" Поскольку ответа нет, компилятор не может этого сделать.

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

#include <iostream>

template<int i>
void modify()
{ std::cout << "modify<"<<i<<">"<< std::endl; }

template<int x, int to>
struct static_for
{
    void operator()() 
    {  modify<x>();  static_for<x+1,to>()(); }
};

template<int to>
struct static_for<to,to>
{
    void operator()() 
    {}
};


int main()
{
    static_for<0,10>()();
}

Обратите внимание, что, делая это, вы фактически создаете 10 экземпляров функций с именем modify<0>... modify<9>, вызываемых соответственно static_for<0,10>::operator()... static_for<9,10>::operator().

static_for<10,10> заканчивается, потому что static_for<10,10> будет создан из специализации, которая принимает два одинаковых значения, которые ничего не делают.

Ответ 2

Поскольку вы попросили ответ, используя Boost.MPL:

#include <boost/mpl/for_each.hpp>
#include <boost/mpl/range_c.hpp>

#include <iostream>

template <int N>
void modify()
{
    std::cout << N << '\n';
}

// You need to wrap your function template in a non-template functor
struct modify_t
{
    template <typename N>
    void operator()(N)
    {
        modify<N::value>();
    }
};

int main()
{
    namespace mpl = boost::mpl;

    mpl::for_each< mpl::range_c<int,0,10> >( modify_t() ); // prints 0 to 9
}

Ответ 3

  • "Почему компилятор не может оценить i во время компиляции?"

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

  • "Есть ли другие возможности для достижения цели, которую я пытаюсь достичь без изменения интерфейса API?"

    Да, посмотрите Boost.MPL.

    Однако я подозреваю, что правильный ответ заключается в том, что вы хотите изменить API. Это зависит от внутренних функций modify. Я знаю, что у вас есть источник, потому что шаблоны должны быть определены в заголовках. Поэтому посмотрите, почему он должен знать i во время компиляции, и если это не так, было бы лучше заменить (или добавить, если вам нужно поддерживать обратную совместимость) с нормальной функцией с параметром.

Ответ 4

Без использования struct или Boost также можно выполнить:

#include <iostream>
#include <utility>

template <int a>
void modify()
{
    std::cout<<a<<",";
}

template<int i,size_t... t>
constexpr inline void CT_for_impl(std::integer_sequence<size_t,t...>)
{
    bool kai[]= { (modify<i+t>(), false)...};
}

template<int i,int n>
constexpr inline void CT_for()
{
    CT_for_impl<i>(std::make_index_sequence<n-i+1>());
}

int main()
{
    CT_for<-5,5>();
    return 0;
}

Ответ 5

решение ошибки: "i" не может появляться в константном выражении для указанной проблемы

Чтобы прочитать о constexpr, нажмите эту ссылку

#include <iostream>
using namespace std;

template <typename T>
void modify(T a)
{
    cout<<a<<endl;  //to check if its working 
}


//func converts int a into const int a
constexpr int func(int a)
{
    return a;
}

int main(){
    for(int i=0; i<10; i++){
        modify(func(i));//here passing func(i) returned value which can be used as template argument now as it is converted to constexpr    
}
    return 0;
}

Ответ 6

Учитывая, что вы хотите вызвать функции во время выполнения по их индексу, и вы не можете изменить API, вы можете рассмотреть стирание типа:

std::vector<std::function<void(int)> > func;
func.push_back(modify<1>);
func.push_back(modify<2>);
//... and so on ...
func.push_back(modify<10>);

for(int i=0; i<10; ++i)
{
    func[i]();  //calls modify<i+1>();
}

Некоторые моменты:

  • Это не те шаблоны, для которых это прежде всего, но это способ принести статическую библиотеку во внешний мир. Основное требование для этого состоит в том, что один работает с однородными типами (-if modify<7>() вернется, скажем, a std::string, весь подход сломается).
  • Предыдущее решение, использующее стирание типа, имеет накладные расходы. Возможно, это ускорится с помощью указателей функций, но все равно он будет всегда медленнее, чем вызов функций во время компиляции.
  • Можно (и должно) также обернуть push_back в другую итеративную статическую функцию, чтобы избежать ручных вызовов.