Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/templates/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 编译器如何处理编译时分支?_C++_Templates_If Statement_C++11_Typetraits - Fatal编程技术网

C++ 编译器如何处理编译时分支?

C++ 编译器如何处理编译时分支?,c++,templates,if-statement,c++11,typetraits,C++,Templates,If Statement,C++11,Typetraits,编辑:我以if/else为例,它有时可以在编译时解决,例如当涉及静态值时,cf。将下面的答案应用于其他类型的静态分支,例如,多分支或多标准分支,应该很简单。注意,使用模板元编程的编译时分支不是这里的主题 在这样的典型代码中 #include <type_traits> template <class T> T numeric_procedure( const T& x ) { if ( std::is_integral<T>::value )

编辑:我以if/else为例,它有时可以在编译时解决,例如当涉及静态值时,cf。将下面的答案应用于其他类型的静态分支,例如,多分支或多标准分支,应该很简单。注意,使用模板元编程的编译时分支不是这里的主题

在这样的典型代码中

#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
    }
}
当我稍后在代码中定义特定的模板类型时,编译器会优化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
}
这些解决方案在性能方面是否存在差异?有没有任何非主观的理由说一个比另一个好?还有其他更好的解决方案来处理编译时分支吗?

编译器可能足够聪明,可以用两种不同的函数实现替换if语句体,只需选择正确的一种。但截至2014年,我怀疑是否有足够聪明的编译器能够做到这一点。不过我可能错了。再想一想,std::is_积分非常简单,我认为它会被优化掉

您对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
}
可以正常工作,因为预处理器会在编译器看到死分支之前将其去掉,但是:

int foo() {
  if (std::is_integral<double>::value) {
    return std::cout << "this isn't going to work\n";
  } else {
    return 1;
  }
}
不会的。即使优化器可以放弃第一个分支,它仍然无法编译。这就是使用enable_if和SFINAE help的地方,因为您可以选择有效的可编译代码,而无效的不可编译代码编译失败不是错误。

TL;博士 有几种方法可以根据模板参数获得不同的运行时行为。性能不应该是您主要关心的问题,但灵活性和可维护性应该是。在所有情况下,各种精简包装器和常量条件表达式都将在任何合适的编译器上进行优化,以便发布版本。下面是一个由@AndyProwl启发的各种权衡的小结

运行时如果 您的第一个解决方案是简单的运行时解决方案,如果:

它工作得很好,没有运行时开销:临时std::is_integral和对单行helper函数的调用都将在任何合适的平台上进行优化

IMO的主要缺点是,您有一些带有3而不是1函数的样板文件

斯菲奈 与标签调度密切相关的是SFINAE替换失败不是错误

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
}
与您的运行时if一样,所有内容都在一个位置,但这里的主要优点是,else分支将完全由编译器删除,而已知它没有被执行。一个很大的优点是,您可以将所有代码保持在本地,并且不必像在标记分派或部分模板专门化中那样使用小的帮助函数

概念Lite C++1z提案

概念Lite是一个计划,它是下一个主要的C++发布C++ 1Z的一部分,Z==7作为最佳猜测。

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
}
这种方法将模板<>括号内的class或typename关键字替换为描述代码应该用于的类型族的概念名称。它可以看作是标签调度和SFINAE技术的推广。一些编译器gcc、Clang对这个特性有实验性的支持。Lite形容词指的是失败的概念C++11提案

归功于

作为静态if工作-编译器仅转到true分支


另外,由于以下原因,您需要使用self=*this并从中拨打会员电话。如果有嵌套的lambda调用,则无法回答有关编译器如何处理iFalse的标题问题:

它们优化了恒定的分支条件和死代码 当然,语言标准并不要求编译器不可怕,但是人们实际使用的C++实现方式并不是很糟糕。大多数C实现也是如此,除了可能非常简单的非优化实现,如

< C++是围绕IF-而不是C预处理器的IFDEF设计的一个主要原因,就是它们同样有效。在编译器实现了必要的优化内联+常量传播之后,许多C++特性如StutExPR只被添加。我们之所以容忍C和C++的所有未定义行为陷阱,是因为性能,特别是在没有UB的假设下,积极优化的现代编译器。语言设计通常不会带来不必要的性能成本

但是,如果您关心调试模式性能 mance,选择可能与编译器有关。e、 g.对于游戏或其他程序,调试构建甚至是可测试的实时要求

e、 g.clang++-O0调试模式仍然在编译时计算ifconstexpr_函数,并将其视为iffalse或iftrue。其他一些编译器只有在模板匹配迫使它们在编译时才进行求值

启用优化后,iFalse没有性能成本。排除遗漏的优化错误,这可能取决于在编译过程中有多早可以将条件解析为false,死代码消除可以在编译器考虑为其变量保留堆栈空间或函数可能是非叶函数或其他任何情况之前将其删除

任何不可怕的编译器都可以在编译时常量条件下优化掉死代码。这是人们对C++实现在现实世界中可用的基线期望的一部分;这是最基本的优化之一,所有实际使用的编译器都是针对constexpr这样的简单情况进行优化的

通常,常量传播(尤其是内联之后)会使条件成为编译时常量,即使它们在源代码中不是很明显。其中一个更明显的情况是,对于int i=0,在a的第一次迭代中优化比较;我是0。是的,真正的编译器进行值范围优化,而不仅仅是常数传播

有些编译器,如gcc和clang,即使在调试模式下,也会通过其内部的arch中性表示以最低级别删除iffalse中的死代码,并最终发出asm。但调试模式会禁用源中未声明为const或constexpr的变量的任何类型的常量传播

某些编译器仅在启用优化时才执行此操作;例如,MSVC在调试模式中确实喜欢在C++中转换为ASM,并且实际上在登记器中创建一个零,并且在它的分支上为零或不用于iffalSE。 对于gcc调试模式-O0,如果不需要,constexpr函数不会内联。在某些地方,语言需要一个常量,比如结构中的数组大小。GNUC++支持C99 VLAS,但选择在StutcReP中嵌入一个CONTXPR函数而不是实际生成VLA。 但非函数constexpr在编译时得到评估,而不是存储在内存中并进行测试

但重申一下,在任何优化级别上,constexpr函数都是完全内联和优化的,然后

例子

调试模式代码质量,通常不相关 禁用优化的GCC仍对表达式求值,并执行死代码消除:

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
要查看gcc未内联某些已禁用优化的内容

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();
}
已禁用优化的MSVC死气沉沉的文字代码生成器:

void foo() {
    if (false) f1();
    else f2();
}
禁用优化的基准测试没有用处 您应该始终为实际代码启用优化;调试模式性能唯一重要的时候是当这是可调试性的先决条件时。它不是一个有用的代理,可以避免基准优化的消失;不同的代码或多或少地从调试模式中获益,这取决于它的编写方式


除非这对你的项目来说真的很重要,而且你找不到足够的关于局部变量的信息,或者像g++-Og这样的最小优化的东西,否则这个答案的标题就是完整的答案。忽略调试模式,只考虑优化构建中asm的质量。如果您的项目能够启用LTO以允许跨文件内联,则最好启用LTO。

是否有任何非主观的理由可以说其中一个优于另一个?前者可能会发出警告,后者则不会。在我所知道的任何实现中,它们都将编译成相同的机器代码。@ildjarn谢谢,我认为这听起来像是一个答案;你愿意详细说明一下吗?这是一种真正的优化技术,即使对于动态参数,它也只能接受几个值bools和enum,编译器会生成单独的函数并根据参数分派它们。例如,void foobool b{如果b_uuufoo_u为真,否则b_uufoo_u为假;}@KerrekSB:与ildjarn相同,这听起来像是我的答案!我们有很好的答案,适合常见问题解答。但是我认为这个问题应该使用if作为编译时可以解决的所有分支的示例,而不是只询问if。此外,应该避免使用术语“静态if”,因为它的含义与这里的实际用法相反。谢谢,启用ifs和SFINAE是我很少接触的两件事,因为我不知道它们是如何工作的。但这很好:您确实意识到函数是一个模板,因此不同的函数不会生成不同的代码,对于任何固定的T,std::is_integral::value是编译时常量吗?删除不适用的分支应该是一个简单的内联、不断折叠和消除死代码的过程。事实上,所有模板元程序都是最小的
g依赖于这些优化来达到近乎高效的效果。@delnan:re因此不同,它不会生成不同的代码,也不会生成不同的专门化。显然,OP需要不同的浮点和整型代码。整数类型的代码,例如使用%,甚至可能无法编译浮点类型的代码。为什么仅仅一眼就有4名支持者对你的评论投了赞成票,这似乎是为了误导,而且在技术上毫无意义,这是一个谜。@cheers-sandhth.-Alf-Different如果他们生成代码的话,他们不会生成不同的代码。它们也可能不起作用,这是一个单独的问题,您的答案没有提到这两个问题。但是,每个使用不同T的调用都会创建一个新的实例化,该实例化将分别进行分析、优化和编码。我的评论既没有误导性,也没有任何意义,它指出,正如你所做的那样,在代码中编辑是完全可以优化的。谢谢,你能给我一个带有enable_if和/或SFINAE的例子吗?我想这是另一个待解决的问题,但如果我有时间,我可以编辑。哇,对不起,我之前没有看到这个,这是一个惊人的答案@谢谢,你不可能早一点看到它,因为我昨天发布了它:不是C++ 11/14,或者任何C++!做模板不是更有意义吗,因为这样可以在模板和模板之间做出细微的区别?与模板相比。此外,还可以引入模板参数应遵守的多个条件。回答得很好。回答得很好,是@TemplateRex总结的一个很好的补充:
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