您的位置:首页 > 汽车 > 新车 > 编译期计算

编译期计算

2024/9/8 8:11:42 来源:https://blog.csdn.net/flyingshineangel/article/details/138859002  浏览:    关键词:编译期计算

        关于编译期计算,直接能够想到的应用是决定是否启用某个模板,或者多个模板之间做选择。但如果有足够多的信息,编译器甚至可以计算控制流的结果。

模板元编程

        模板元编程的简单例子,如下:

#include <iostream>template <unsigned p, unsigned d>
struct DoIsPrimer {static constexpr bool value = (p % d != 0) && DoIsPrimer<p, d - 1>::value;
};template <unsigned p>
struct DoIsPrimer<p, 2> {static constexpr bool value = (p % 2 != 0);
};template <unsigned p>
struct IsPrimer {static constexpr bool value = DoIsPrimer<p, p / 2>::value;
};template <>
struct IsPrimer<0> { static constexpr bool value = false; };template <>
struct IsPrimer<1> { static constexpr bool value = false; };template <>
struct IsPrimer<2> { static constexpr bool value = true; };template <>
struct IsPrimer<3> { static constexpr bool value = true; };//仅为证明自己算发生在编译阶段
template <unsigned p, typename enable = typename std::enable_if<IsPrimer<p>::value>::type>
class Primer {
};int main(int argc, char **argv)
{Primer<3> primer3;Primer<9> primer9;return 0;
}

        上面的例子Primer<3> primer3;通过编译,Primer<9> primer9;编译报错,这足以证明IsPrimer在编译阶段完成了计算,其展开步骤如下:

    IsPrime<9>::value
=>  DoIsPrime<9,4>::value
=>  9%4!=0 && DoIsPrime<9,3>::value
=>  9%4!=0 && 9%3!=0 && DoIsPrime<9,2>::value
=>  9%4!=0 && 9%3!=0 && 9%2!=0
=>  false  

通过 constexpr 进行计算

        c++11开始引入了constexpr特性,大大简化了编译器运算。但对于constexpr使用,c++11拥有诸多限制,如constexpr的定义只能包含一个return语句。这些限制从c++14开始,大部分被移除。但为了所有计算步骤都能够在编译其进行,目前所有的c++版本constexpr函数都不支持异常抛出和内存分配。下面是constexpr版的IsPrimer:

#include <iostream>#if 1
//c++11实现
constexpr bool DoIsPrimer(unsigned p, unsigned d) { return d != 2 ? (p % d != 0) && DoIsPrimer(p, d - 1) : (p % 2 != 0); }constexpr bool IsPrimer(unsigned p) { return p < 4 ? !(p < 2) : DoIsPrimer(p, p / 2); }
#else
//c++14实现
constexpr bool IsPrimer(unsigned p) {for (unsigned d = 2; d < p / 2; ++d) {if (p % d == 0)return false;}return p > 1;
}
#endif//仅为证明自己算发生在编译阶段
template <unsigned p, typename enable = typename std::enable_if<IsPrimer(p)>::type>
class Primer {
};int main(int argc, char **argv)
{Primer<3> primer3;Primer<9> primer9;return 0;
}

         需要注意一点,“可以”在编译期计算,并非“一定”在编译期计算。计算发生在编译还是运行,先来做一个实验:

//...
bool is_p0 = IsPrimer(0); //==在编译期计算
const bool is_p1 = IsPrimer(1); //==在编译期计算
constexpr bool is_p2 = IsPrimer(2); //==在编译期计算
int p3 = 3;
bool is_p3 = IsPrimer(p3); //在运行期计算
int p4 = 4;
const bool is_p4 = IsPrimer(p4); //在运行期计算
const int p5 = 5;
constexpr bool is_p5 = IsPrimer(p5); //==在编译期计算int main(int argc, char **argv)
{Primer<3> primer3;bool is_p6 = IsPrimer(6); //在运行期计算int p7 = 7;bool is_primer = IsPrimer(p7); //在运行期计算const int p8 = 8;constexpr bool is_p8 = IsPrimer(p8); //==在编译期计算return 0;
}

        关于上面的实验, 通过在IsPrime设置断点即可判断计算发生在编译期还是运行期。通过实验,可以发现下面的规律:

  • 如果计算在全局,结果变量为constexpr或输入参数为常量,计算都会在编译期触发
  • 如果计算在局部,结果变量为constexpr并且输入参数为常量,计算才会在编译期触发,否则延迟到运行期

编译器if

        c++17引入了if constexpr(...)语法,编译器根据该语法在编译期决定使用if部分还是else部分的代码。该语法主要用于两个场景:

  • 变参模板

        使用编译器if判断参数数量,当参数为0时,不再执行任何语句,解除对void print(void)函数的依赖。此处编译期if不可被普通if取代,print函数族的迭代生成实在编译器完成的,如果使用普通if,编译时会报错“No matching function for call to 'print' ”,因为print模板不会生成参数为0的函数,当编译迭代到...args为0时,发现没有合适的函数调用报错。

#include <iostream>template <typename T, typename ...Ts>
void print(const T &arg, const Ts &...args)
{std::cout << arg << std::endl;if constexpr(sizeof...(args) > 0) {print(args...);}
}int main(int argc, char **argv)
{print("Jim", 'M', 30);return 0;
}
  • std::is_xxx函数族

        通过使用std::is_xxx函数族判断,不同的的情况使用不同的代码。下面是一个例子,如果T是整形族,则执行递归;否则直接返回原值。此处是否可用普通if语句代替编译器if语句?答案是否定的。如果使用普通if语句,编译时,整个函数的所有语句都会被编译,当类型为string时,由于不支持和整型的比较,及-和*操作符,arg == 1和arg * factorial(arg - 1)都会报错。

template <typename T>
constexpr T factorial(T arg)
{if constexpr(std::is_integral_v<T>) {if (arg == 1)return arg;return arg * factorial(arg - 1);}else {///static_assert(false, "argument is not integral!"); ///编译期总是会被触发,不管是否会用到该段代码static_assert(!std::is_integral_v<T>, "argument is not integral!"); //总是不会被触发,即便该段代码被用到std::cout << arg<< std::endl;return arg;}
}int main(int argc, char **argv)
{std::cout << factorial(5) << std::endl;std::cout << factorial("hello") << std::endl;return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com