【问题标题】:Passing a variable as a template argument将变量作为模板参数传递
【发布时间】:2012-06-18 11:09:31
【问题描述】:

我正在使用一个库,它公开了一个可以使用的接口。这个库的功能之一是这样的:

template <int a>
void modify(){}

我必须将参数从 1 修改为 10,即使用从 1 到 10 的模板参数调用 modify。为此,我编写了这段代码(代码的基本版本,实际代码要大得多)。

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

在编译时我收到以下错误

error: 'i' cannot appear in constant-expression

在浏览了互联网上的一些链接后,我知道我不能将任何值作为模板参数传递,而模板参数在编译时不会被评估。 我的问题如下: 1. 为什么编译器不能在编译时评估i? 2.在不改变API接口的情况下,还有其他方法可以实现我想要实现的目标吗?


我还有一件事要做。调用 modify as modify 其中 VAR 是某些函数计算的输出。我怎样才能做到这一点?

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    i 在编译时的值是多少(不是常量)?除非执行循环,否则无法回答。但执行不是“编译” 由于没有答案,编译器无法做到这一点。

    模板不是要执行的算法,而是要扩展以生成代码的宏。 你可以做的是依靠特化通过递归来实现迭代,就像这里:

    #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&lt;0&gt; ... modify&lt;9&gt;,分别由static_for&lt;0,10&gt;::operator() ... static_for&lt;9,10&gt;::operator()调用。

    迭代结束,因为 static_for&lt;10,10&gt; 将从采用两个相同值的特化中实例化,这什么都不做。

    【讨论】:

    • @Emilio 有没有办法调用 modify 其中 VAR 是某个函数计算返回的变量,比如说 VAR= f() ?
    • @gibraltar:只有当它是 C++11 并且函数被标记为 constexpr 时(而不是你可以写 modify&lt;f()&gt;)。请注意,constexpr 的规则非常严格。
    • @gibraltar: 或者如果函数不是常数,但它的范围很小,你可以写像switch(f()) { case 0: modify&lt;0&gt;(); break; case 1: modify&lt;1&gt;(); .... }
    • 如果编译器能够确定允许展开循环,那就太好了。
    【解决方案2】:
    1. “为什么编译器不能在编译时评估i?”

      这会破坏模板的目的。模板适用于源代码在某些情况下看起来相同,但编译器需要生成的指令每次都不同的情况。

    2. “在不更改 API 接口的情况下,还有其他方法可以实现我想要实现的目标吗?”

      是的,看看Boost.MPL

      但是我怀疑这里的正确答案是您想要更改 API。这取决于modify 函数的内部结构。我知道你有它的来源,因为模板必须在标题中定义。所以看看为什么它需要在编译时知道i,如果不需要,最好用带参数的普通函数替换(或补充,如果你需要保持向后兼容性)。

    【讨论】:

    • 您能详细说明一下Boost.MPL的使用吗?我不熟悉这个库。
    【解决方案3】:

    既然您使用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
    }
    

    【讨论】:

      【解决方案4】:

      不使用 structBoost 也可以:

      #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'不能出现在常量表达式中

        To read about constexpr click this link

        #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;
        }
        

        【讨论】:

        • 这不是 OP 的意图。首先,仅将运行时整数 constexpr y 传递给相应的函数是行不通的……它就是行不通。但即使是这样,你也会调用modify&lt;int&gt;,而不是 OP 要求的非类型版本。
        • 但是模板会被自动检测到
        【解决方案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>();
        }
        

        需要提及的几点:

        • 这不是模板的主要用途,但它是将静态库引入运行时世界的一种方式。对此的基本要求是使用同构类型(--如果modify&lt;7&gt;() 会返回,比如说,std::string,整个方法就会中断)。
        • 以前使用类型擦除的解决方案有开销。使用函数指针可能会更快,但它总是比在编译时调用函数要慢。
        • 也可以(并且应该)将push_backs 包装到另一个迭代静态函数中以避免手动调用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-10-11
          • 1970-01-01
          • 2020-02-23
          • 1970-01-01
          • 2023-03-10
          相关资源
          最近更新 更多