C++ 编译器如何处理编译时分支?
EDIT:我以“if/else”为例,有时可以在编译时解决(例如当涉及静态值时,cfC++ 编译器如何处理编译时分支?,c++,templates,if-statement,c++11,typetraits,C++,Templates,If Statement,C++11,Typetraits,EDIT:我以“if/else”为例,有时可以在编译时解决(例如当涉及静态值时,cf)。将下面的答案应用于其他类型的静态分支(例如,多分支或多标准分支)应该很简单。注意,使用模板元编程的编译时分支不是这里的主题 在这样的典型代码中 #include <type_traits> template <class T> T numeric_procedure( const T& x ) { if ( std::is_integral<T>::va
)。将下面的答案应用于其他类型的静态分支(例如,多分支或多标准分支)应该很简单。注意,使用模板元编程的编译时分支不是这里的主题
在这样的典型代码中
#include <type_traits>
template <class T>
T numeric_procedure( const T& x )
{
if ( std::is_integral<T>::value )
{
// Integral types
}
else
{
// Floating point numeric types
}
}
#包括
模板
T数值_程序(常数T&x)
{
if(std::is_integral::value)
{
//整型
}
其他的
{
//浮点数字类型
}
}
当我稍后在代码中定义特定的模板类型时,编译器会优化if/else语句吗
一个简单的替代方法是这样写:
#include <type_traits>
template <class T>
inline T numeric_procedure( const T& x )
{
return numeric_procedure_impl( x, std::is_integral<T>() );
}
// ------------------------------------------------------------------------
template <class T>
T numeric_procedure_impl( const T& x, std::true_type const )
{
// Integral types
}
template <class T>
T numeric_procedure_impl( const T& x, std::false_type const )
{
// Floating point numeric types
}
#包括
模板
内联T数值_过程(常数T&x)
{
返回数值_过程_impl(x,std::is_integral());
}
// ------------------------------------------------------------------------
模板
数值程序(常数T&x,标准::真值类型常数)
{
//整型
}
模板
数字程序执行(常数T&x,标准::假常数类型)
{
//浮点数字类型
}
这些解决方案在性能方面是否存在差异?有没有任何非主观的理由说一个比另一个好?还有其他(可能更好)的解决方案来处理编译时分支吗?编译器可能足够聪明,可以用两种不同的函数实现替换if
语句体,只需选择正确的一种。但截至2014年,我怀疑是否有足够聪明的编译器能够做到这一点。不过我可能错了。仔细想想,std::is_integral
非常简单,我认为它会被优化掉
您对std::is_integral
的结果重载的想法是一种可能的解决方案
另一个更干净的解决方案是使用std::enable_if
(以及std::is_integral
)。请注意,尽管优化器可能能够从生成的代码中删除静态已知的测试和无法访问的分支,但编译器仍然需要能够编译每个分支
即:
int foo() {
#if 0
return std::cout << "this isn't going to work\n";
#else
return 1;
#endif
}
intfoo(){
#如果0
返回STD:太长了,读不下去了。
有几种方法可以根据模板参数获得不同的运行时行为。性能不应该是您主要关心的问题,但灵活性和可维护性应该是。在所有情况下,各种精简包装和常量条件表达式都将在任何合适的编译器上进行优化下面是一个包含各种权衡的小摘要(灵感来自@AndyProwl)
运行时如果
您的第一个解决方案是简单的运行时if
:
template<class T>
T numeric_procedure(const T& x)
{
if (std::is_integral<T>::value) {
// valid code for integral types
} else {
// valid code for non-integral types,
// must ALSO compile for integral types
}
}
它工作正常,没有运行时开销:临时的std::is_integral()
和对单行助手函数的调用都将在任何合适的平台上进行优化
主要(次要的IMO)缺点是您有一些带有3个而不是1个函数的样板文件
斯菲奈
与标签调度密切相关的是SFINAE(替换失败不是错误)
与运行时的if
一样,所有内容都在一个地方,但这里的主要优点是else
分支在已知未被使用时将被编译器完全删除。一个巨大的优点是,您将所有代码保持在本地,并且不必像在标记分派或部分模板中那样使用小的辅助函数专业化
概念精简版(C++1z提案)
概念Lite是一个计划,它是下一个主要C++发布的一部分(C++ +1Z,以<代码> z=7 为最佳猜测)。
模板
T数值_程序(常数T&x)
{
//非整数类型的有效代码,
//可以包含对整型无效的代码
}
模板
T数值_程序(常数T&x)
{
//整数类型的有效代码
}
这种方法将模板
括号内的类
或类型名
关键字替换为描述代码应该用于的类型族的概念名。它可以被视为标记分派和SFINAE技术的推广。一些编译器(gcc、Clang)对此功能提供实验支持。Lite形容词指的是失败的概念C++11提案。归功于
如果编译器只转到“true”分支,则其工作方式为静态
另外,由于以下原因,您需要使用self=*this
并从中进行成员调用。如果要使用嵌套的lambda调用,您无法回答编译器如何处理If(false)
的标题问题:
它们优化了恒定的分支条件(和死代码)
<>语言标准当然不要求编译器不可怕,但是人们实际使用的C++实现方式不是很可怕。(大多数C实现都是这样,除了可能非常简单的非优化的类)。
<> > C++中围绕C++设计的一个主要原因,如果(某物)而不是C预处理器的代码>代码IF.DEF F < /COD>是同样有效的。许多C++特性(如 CONEXPROP>代码>)在编译器实现必要的优化(内联+常量传播)之后,只被添加了。(我们忍受C和C++所有未定义行为陷阱的原因是性能,特别是在没有UB的假设下,积极地优化编译器。语言设计通常不会造成不必要的性能成本。)
但如果您关心调试模式的性能,则选择可能与编译器有关。(例如,对于带有r的游戏或其他程序)
template<class T>
T numeric_procedure(const T& x)
{
if (std::is_integral<T>::value) {
// valid code for integral types
} else {
// valid code for non-integral types,
// must ALSO compile for integral types
}
}
template<class T>
T numeric_procedure_impl(const T& x, std::false_type)
{
// valid code for non-integral types,
// CAN contain code that is invalid for integral types
}
template<class T>
T numeric_procedure_impl(const T& x, std::true_type)
{
// valid code for integral types
}
template<class T>
T numeric_procedure(const T& x)
{
return numeric_procedure_impl(x, std::is_integral<T>());
}
template<class T, class = typename std::enable_if<!std::is_integral<T>::value>::type>
T numeric_procedure(const T& x)
{
// valid code for non-integral types,
// CAN contain code that is invalid for integral types
}
template<class T, class = typename std::enable_if<std::is_integral<T>::value>::type>
T numeric_procedure(const T& x)
{
// valid code for integral types
}
template<class T, bool>
struct numeric_functor;
template<class T>
struct numeric_functor<T, false>
{
T operator()(T const& x) const
{
// valid code for non-integral types,
// CAN contain code that is invalid for integral types
}
};
template<class T>
struct numeric_functor<T, true>
{
T operator()(T const& x) const
{
// valid code for integral types
}
};
template<class T>
T numeric_procedure(T const& x)
{
return numeric_functor<T, std::is_integral<T>::value>()(x);
}
template<class T>
T numeric_procedure(const T& x)
{
if constexpr (std::is_integral<T>::value) {
// valid code for integral types
} else {
// valid code for non-integral types,
// CAN contain code that is invalid for integral types
}
}
template<Non_integral T>
T numeric_procedure(const T& x)
{
// valid code for non-integral types,
// CAN contain code that is invalid for integral types
}
template<Integral T>
T numeric_procedure(const T& x)
{
// valid code for integral types
}
template<class FN1, class FN2, class ...Args>
decltype(auto) if_else_impl(std::true_type, FN1 &&fn1, FN2 &&, Args&&... args)
{
return fn1(std::forward<Args>(args)...);
}
template<class FN1, class FN2, class ...Args>
decltype(auto) if_else_impl(std::false_type, FN1 &&, FN2 &&fn2, Args&&... args)
{
return fn2(std::forward<Args>(args)...);
}
#define static_if(...) if_else_impl(__VA_ARGS__, *this)
static_if(do_it,
[&](auto& self){ return 1; },
[&](auto& self){ return self.sum(2); }
);
#include <type_traits>
void baz() {
if (std::is_integral<float>::value) f1(); // optimizes for gcc
else f2();
}
baz():
jmp f2() # optimized tailcall
baz():
push rbp
mov rbp, rsp # -fno-omit-frame-pointer is the default at -O0
call f2() # still an unconditional call, no runtime branching
nop
pop rbp
ret
static constexpr bool always_false() { return sizeof(char)==2*sizeof(int); }
void baz() {
if (always_false()) f1();
else f2();
}
static constexpr bool always_false() { return sizeof(char)==2*sizeof(int); }
void baz() {
if (always_false()) f1();
else f2();
}
;; gcc9.1 with no optimization chooses not to inline the constexpr function
baz():
push rbp
mov rbp, rsp
call always_false()
test al, al # the bool return value
je .L9
call f1()
jmp .L11
.L9:
call f2()
.L11:
nop
pop rbp
ret
void foo() {
if (false) f1();
else f2();
}
;; MSVC 19.20 x86-64 no optimization
void foo(void) PROC ; foo
sub rsp, 40 ; 00000028H
xor eax, eax ; EAX=0
test eax, eax ; set flags from EAX (which were already set by xor)
je SHORT $LN2@foo ; jump if ZF is set, i.e. if EAX==0
call void f1(void) ; f1
jmp SHORT $LN3@foo
$LN2@foo:
call void f2(void) ; f2
$LN3@foo:
add rsp, 40 ; 00000028H
ret 0