使用int作为直到运行时才知道的模板形参

Using an int as a template parameter that is not known until run-time

本文关键字:形参 运行时 int 使用      更新时间:2023-10-16

我试图使用一个整数作为一个类的模板参数。下面是一个示例代码:

template< int array_qty > 
class sample_class {
    public:
        std::array< std::string, array_qty > sample_array;
}

如果我这样做,它会工作:

sample_class< 10 > sample_class_instance;

然而,假设我在编译时不知道array_qty(模板参数)的值,只在运行时知道。在这种情况下,我将传递一个int变量作为模板参数。为了便于演示,下面的代码不起作用:

int test_var = 2;
int another_test_var = 5;
int test_array_qty = test_var * another_test_var;
sample_class< test_array_qty > sample_class_instance;

在编译时,我得到以下错误,当尝试上面的:

the value of ‘test_array_qty’ is not usable in a constant expression

我已经尝试将test_array_qty转换为const,同时将其作为模板参数传递,但这似乎也不起作用。有什么方法可以做到这一点,还是我滥用模板参数?也许它们需要在编译时就知道?

我们的目标不是解决这个特定的方法,而是找到一种方法,将数组的长度设置为一个int变量,可以在实例化类时声明。如果有办法通过模板参数来实现这一点,那将是理想的。

请注意,我必须使用一个数组,而不是一个向量,我可能最终作为一个建议。此外,array_qty将始终是0到50之间的值——以防产生差异。

这是可以做到的。但请相信我,你问错了问题。因此,下面的内容回答了你的问题,尽管这样做几乎总是一个坏主意。

你实际上可以做的是创建50个不同的程序,每个程序对应50种可能的大小,然后有条件地跳转到你想要的那个。

template<int n>
struct prog {
  void run() {
    // ...
  }
};

template<int n>
struct switcher {
  void run(int v) {
    if(v==n)
      prog<n>::run();
    else
      switcher<n-1>::run(v);
  }
};
template<>
struct switcher<-1> {
  void run(int v){
  }
};

调用switcher<50>::run( value );,如果value为0 ~ 50,则调用prog<value>::run()。在prog::run中,模板参数是编译时的值。

可怕的黑客,你可能会更好地使用另一个解决方案,但这是你所要求的。

下面是c++ 14基于表的版本:

template<size_t N>
using index_t = std::integral_constant<size_t, N>; // C++14
template<size_t M>
struct magic_switch_t {
  template<class F, class...Args>
  using R=std::result_of_t<F(index_t<0>, Args...)>;
  template<class F, class...Args>
  R<F, Args...> operator()(F&& f, size_t i, Args&&...args)const{
    if (i >= M)
      throw i; // make a better way to return an error
    return invoke(std::make_index_sequence<M>{}, std::forward<F>(f), i, std::forward<Args>(args)...);
  }
private:
  template<size_t...Is, class F, class...Args>
  R<F, Args...> invoke(std::index_sequence<Is...>, F&&f, size_t i, Args&&...args)const {
    using pF=decltype(std::addressof(f));
    using call_func = R<F, Args...>(*)(pF pf, Args&&...args);
    static const call_func table[M]={
      [](pF pf, Args&&...args)->R<F, Args...>{
        return std::forward<F>(*pf)(index_t<Is>{}, std::forward<Args>(args)...);
      }...
    };
    return table[i](std::addressof(f), std::forward<Args>(args)...);
  }
};

magic_switch_t<N>{}( f, 3, blah1, blah2, etc )将调用f(index_t<3>{}, blah1, blah2, etc)

一些c++ 14编译器会阻塞包含lambda的可变包展开。这不是必须的,你可以做一个变通,但变通是丑陋的。

c++ 14的所有特性都是可选的:你可以在c++ 11中实现它们,但是还是很难看。

传递的f基本上应该是一个函数对象(以auto作为第一个参数的lambda,或手动的)。直接传递函数名不能很好地工作,因为当第一个参数成为编译时值时,上面的方法最有效。

你可以用lambda或函数对象来包装函数模板。

对于c++ 11,非类型模板参数被限制为以下(§14.3.2/1):


非类型、非模板模板形参的模板实参必须是:
  • 对于整型或枚举型的非类型模板形参,为模板形参类型的转换常量表达式(5.19);或
  • 非类型模板参数的名称;或
  • 一个常量表达式(5.19),它指定具有静态存储时间和外部或内部链接的对象的地址,或具有外部或内部链接的函数的地址,包括函数模板和函数模板id,但不包括非静态类成员,表示为(忽略括号)&id-表达式,除了&引用函数或数组的可以省略,引用模板形参的可以省略;或
  • 一个计算结果为空指针值的常量表达式(4.10);或
  • 计算结果为空成员指针值的常量表达式(4.11);或
  • 5.3.1中描述的成员指针。

在c++ 98和c++ 03中,列表的限制更大。底线是:你想做的事情是不被允许的。

模板参数必须是编译时常量,即"常量表达式"或简称constexpr s。所以没有办法做的就是使用模板。

可以使用动态大小的数组,并将其大小存储在int中。

或者简单地使用vector。一定要在构造函数中通过将所需的大小传递给vector的构造函数来初始化它的大小!

对不起,这是不可能的。模板参数必须是编译时已知的常量表达式。

我有点晚了,但这是我的建议。我猜矢量的主要问题是,它们分配的容量比支持动态增长所需的容量大。那么,您就不能编写自己的简单数组类吗?

template <typename T>
class Array {
    private: 
        T* data; 
        unsigned size; 
    public: 
        Array(unsigned size) {
            data = new T[size]; 
            this->size = size; 
        }
        T& operator[](int i) {
            return data[i]; 
        }
        T operator[](int i) const {
            return data[i]; 
        }
        // Depending on your needs, maybe add copy constructor and assignment operator here.
        ...
        unsigned size() {
            return size; 
        }
        ~Array() {
            delete [] data; 
        }
}

据我所知,我相信这应该和STL数组类一样快。此外,您可以创建在运行时之前大小未知的数组,数组的内存在销毁时自动处理,并且您不必每次创建具有不同大小的新数组时都实例化一个新类(就像您必须为STL数组所做的那样)。