【问题标题】:Reason for using non-type template parameter instead of regular parameter?使用非类型模板参数而不是常规参数的原因?
【发布时间】:2011-09-13 00:54:21
【问题描述】:

在 C++ 中,您可以使用非类型模板参数创建模板,如下所示:

template< int I >
void add( int& value )
{
  value += I;
}

int main( int argc, char** argv )
{
  int i = 10;
  add< 5 >( i );
  std::cout << i << std::endl;
}

将“15”打印到 cout。这有什么用?是否有任何理由使用非类型模板参数而不是更传统的参数,例如:

void add( int& value, int amount )
{
  value += amount;
}

对不起,如果这已经被问到了(我看了但找不到任何东西)。

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    非类型模板参数有很多应用;这里有一些:

    您可以使用非类型参数来实现表示固定大小的数组或矩阵的泛型类型。例如,您可以在其维度上参数化 Matrix 类型,因此您可以创建 Matrix&lt;4, 3&gt;Matrix&lt;2, 2&gt;。如果您随后为这些类型正确定义重载运算符,则可以防止因添加或乘以不正确维度的矩阵而导致的意外错误,并且可以使函数明确传达它们接受的矩阵的预期维度。这可以通过在编译时检测违规来防止发生大量运行时错误。

    您可以使用非类型参数通过模板元编程实现编译时函数评估。例如,这是一个在编译时计算阶乘的简单模板:

    template <unsigned n> struct Factorial {
        enum { 
           result = n * Factorial<n - 1>::result
        };
    };
    template <> struct Factorial<0> {
        enum {
           result = 1
        };
    };
    

    这允许您编写类似Factorial&lt;10&gt;::result 的代码,以便在编译时获得 10! 的值。这可以防止在运行时执行额外的代码。

    此外,您可以使用非类型参数来实现compile-time dimensional analysis,它允许您定义公斤、米、秒等的类型,以便编译器可以确保您不会在您的意思处意外使用公斤米等。

    希望这会有所帮助!

    【讨论】:

    • 公平地说,编译器可以获得与内联相同的一些优化好处。例如,clanggcc -O3 -O3factorial(10) 都是递归编写的,clang 也成功了 20(实际上是我尝试过的任何值)。 icc 似乎从来没有做过这种优化。
    • 哦,当然。请注意,这个答案是在 2011 年写的,当时情况并非如此。 :-)
    • 我认为值得编辑提及constexpr 作为(IMO)更简单的方法来执行这种仅编译时计算。当然,这在 2011 年不存在,但并不是有一个神奇的障碍阻止这个答案是最新的:)
    【解决方案2】:

    在这种情况下你可能是对的,但有些情况下你需要在编译时知道这些信息:

    但是这个怎么样?

    template <std::size_t N>
    std::array<int, N> get_array() { ... }
    

    std::array 需要在编译时知道它的大小(因为它是在堆栈上分配的)。

    你不能这样做:

    std::array<int>(5);
    

    【讨论】:

      【解决方案3】:

      在那种特殊情况下,实际上并没有任何优势。但是使用这样的模板参数,你可以做很多你不能做的事情,比如有效地将变量绑定到函数(如boost::bind),在函数或类中指定编译时数组的大小(@ 987654322@ 就是一个现成的例子)等等。

      例如,使用该函数,您可以编写类似的函数

      template<typename T>
      void apply(T f) {
          f(somenum);
      }
      

      那么你可以给apply一个函数传递:

      apply(&add<23>);
      

      这是一个非常简单的例子,但它演示了原理。更高级的应用程序包括将函数应用于集合中的每个值,在编译时计算函数的阶乘等。

      你无法以任何其他方式做到这一点。

      【讨论】:

        【解决方案4】:

        嗯,这是编译时多态性和运行时多态性之间的典型选择。

        从您问题的措辞看来,您在“普通”模板参数中没有看到任何异常,而将 非类型 参数视为奇怪和/或多余的东西。实际上,同样的问题也可以应用于模板 type 参数(您所谓的“普通”参数)。相同的功能通常可以通过具有虚函数的多态类(运行时多态)或模板 type 参数(编译时多态)来实现。人们也可以问为什么我们需要模板 type 参数,因为几乎所有东西都可以使用多态类来实现。

        非类型参数的情况下,你可能希望有一天有这样的东西

        template <int N> void foo(char (&array)[N]) {
          ...
        }
        

        无法使用运行时值实现。

        【讨论】:

          【解决方案5】:

          有很多原因,比如进行模板元编程(检查 Boost.MPL)。但是没有必要走那么远,C++11 的std::tuple 有一个访问器std::get&lt;i&gt; 需要在编译时进行索引,因为结果取决于索引。

          【讨论】:

          • 更重要的是,C++03 在std::bitset 中有非类型模板参数。
          【解决方案6】:

          我能想到的值参数最常见的用法是std::get&lt;N&gt;,它检索std::tuple&lt;Args...&gt; 的第N 个元素。第二个最常用的用法是std::integral_constant 及其主要派生词std::true_typestd::false_type,它们普遍存在于任何类型的特征类中。事实上,类型特征绝对充满了值模板参数。特别是,有一些 SFINAE 技术利用签名模板&lt;typename T, T&gt; 来检查类成员的存在。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-05-18
            • 2021-03-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多