C++模板函数优化失败

3

据我了解,编译时评估为false的分支将不会被执行。在下面的代码中,编译器不应该舍弃包含导致错误的行的代码块吗?

#include <iostream>

template<int i1, int i2>
void TemplateFunc()
{
    std::cout << i1 << std::endl;
    if(i2 > 0)
    {
        std::cout << i2 << std::endl;
        // The following causes an error
        // "cannot allocate an array of constant size 0"
        int someThing[i2] = {276};
        std::cout << someThing[i2/2] << std::endl;
    }
}

int main(int argc, char** argv)
{
    TemplateFunc<1,2>();
    TemplateFunc<3,0>();
    return 0;
}

我已经尝试过使用VS 2012,g++(在coliru上使用“g++ -std=c++11 -O3 -Wall -pedantic -pthread main.cpp && ./a.out”)以及在CUDA内核中使用类似代码的nvcc。

2个回答

3

我认为你对编译器的工作方式有所误解。当模板被实例化时,它可能会放弃检查,但是如果编译器要实例化模板,它必须首先进行编译。由于它无法编译,并且没有可用的替代模板可用,因此它将失败并出现编译错误。

如果这是一个模板,您可以通过部分特化来实现这一点:

#include <iostream>

template<int i1, int i2>
class TemplateFunc {
public:
  void operator()() {
    ...code with an if...
  }
};

template<int i1>
class TemplateFunc<i1, 0> {
public:
  void operator()() {
    ...code without and if...
  }
};

int main(int argc, char** argv)  
{
  TemplateFunc<1,2>()();
  TemplateFunc<3,0>()();
  return 0;
}

在这段代码中,由于特化的存在,带有if的模板将不会被编译器选择,因此不会发生编译错误。但是对于函数来说,这种方法在今天的标准下是不允许的。

一个模板函数可以委托给一个模板类的静态方法,该模板类可以被特化。我刚在我的答案中包含了它。:) 顺便说一句,i2也可能是负数,所以我没有简单地为i2 == 0的情况进行特化。 - user743382
@hvd 静态函数是否提供了operator()没有的任何东西?是的,可以添加三个状态作为第三个模板参数来支持负值,但在我看来,我们的解决方案已经足够过度设计了。 - daramarak
TemplateFunc<1,2>() 并不会调用你的 operator()。它只是构造了一个 TemplateFunc<1,2> 对象。如果要调用你的 operator(),你需要写成 TemplateFunc<1,2>()(),但我想避免这种情况。 - user743382

2

模板只会让事情变得更加混乱。来看一个更简单的例子。

int main() {
  if (0) {
    int array[0];
  }
}

这会导致编译时诊断(在符合规范的实现上)。按照您的逻辑,它不应该,因为 if 块永远不会被执行。
理论上,检查 0 是否为零是在运行时进行的,因此编译器必须考虑分支可能被执行的情况。
实际上,编译器确实会优化常量条件,但这是根据 as-if 规则,该规则有效地声明优化不能改变程序的含义。未经优化的程序将具有错误,在编译时将被诊断出来。因此,经过优化的程序仍然存在错误,并在编译时被诊断出来。
有一些方法可以包括必须在编译时执行的检查。其中一种方法是模板部分特化,并从特化中删除如果 i2 不是正数将被执行的有问题的代码。
template<int i1, int i2, bool i2_is_positive>
struct TemplateFuncImpl;

template<int i1, int i2>
struct TemplateFuncImpl<i1, i2, false> {
  static void Impl() {
    std::cout << i1 << std::endl;
  }
};

template<int i1, int i2>
struct TemplateFuncImpl<i1, i2, true> {
  static void Impl() {
    std::cout << i1 << std::endl;
    std::cout << i2 << std::endl;
    int someThing[i2] = {276};
    std::cout << someThing[i2/2] << std::endl;
  }
};

template<int i1, int i2>
void TemplateFunc()
{
    TemplateFuncImpl<i1, i2, (i2 > 0)>::Impl();
}

诚然,以下内容可能过于冗长,可能不是你真正想要使用的。不幸的是,没有简单的方法。@Columbo提到有一些建议提出了一个变体if语句,它会被强制在编译时运行,但实际上并没有进入标准中。


我还想提一下静态if。+1 - Columbo
1
@Columbo 有这样的东西吗?我知道有人提出了这样的建议,但我不知道它们是否被接受。 - user743382
官方上并没有这样的东西。不过,这个想法已经被考虑过了,而且正好符合 OP 所需要的。 (我只是说一下,没什么重要的) - Columbo
@Columbo 我想到了一个更有帮助的方法来包含在我的答案中。 :) 感谢您的建议。 - user743382
感谢您的额外努力,但正如您所指出的那样,那非常冗长,并且为了我正在编写的测试,我不想重新构造程序的各个元素。 - Avi Ginsburg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接