如何使用户能够定义模板功能doIt(a,b)
将从另一个用户定义的变量/函数op
出发的“行为模式”,并优化 if-else ?
这是正在运行的MCVE。
我的VS解决方案有2个项目。 pj1 是我的图书馆。 pj2 是用户的项目。
A_pj1.h
#pragma once
template<int T>class BoolT{public:
static bool op;
};
template<int T> bool BoolT<T>::op=true; //by default, true=+, false=-
template<int i> int doIt(int a,int b){
if(BoolT<i>::op){
return a+b;
}else{ return a-b;}
}
A_pj2_UserDefine.h
#pragma once
#include "A_pj1.h"
inline void A_pj2_UserDefine_Reg(){
BoolT<2>::op=false; //override default value;
}
A_pj2_main.cpp
#include "A_pj2_UserDefine.h"
#include <iostream>
int main(){
A_pj2_UserDefine_Reg();
int s1=doIt<1>(3,2); //= 5 (correct)
int s2=doIt<2>(3,2); //= 1 (correct)
std::cout << s1<<" "<<" "<<s2<<std::endl;
int asfasd=0;
}
(编辑)这是反汇编(优化版本):-
int s2=doIt<2>(3,2);
std::cout << s1<<" "<<" "<<s2<<std::endl;
00B61620 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B6B0A8h)]
int s1=doIt<1>(3,2); //:
00B61626 xor eax,eax
00B61628 cmp byte ptr [BoolT<1>::op (0B6E001h)],al
int s2=doIt<2>(3,2);
std::cout << s1<<" "<<" "<<s2<<std::endl;
00B6162E push offset std::endl<char,std::char_traits<char> > (0B61530h)
00B61633 push 1
int s1=doIt<1>(3,2); //:
00B61635 setne al
A_pj2_UserDefine_Reg();
00B61638 mov byte ptr [BoolT<2>::op (0B6E000h)],0
int s2=doIt<2>(3,2);
std::cout << s1<<" "<<" "<<s2<<std::endl;
00B6163F push offset string " " (0B6B220h)
00B61644 push offset string " " (0B6B220h)
int s1=doIt<1>(3,2); //:
00B61649 lea eax,[eax*4+1]
int s2=doIt<2>(3,2);
std::cout << s1<<" "<<" "<<s2<<std::endl;
00B61650 push eax
00B61651 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6B0B4h)]
00B61657 push eax
00B61658 call std::operator<<<std::char_traits<char> > (0B61310h)
00B6165D add esp,8
00B61660 push eax
00B61661 call std::operator<<<std::char_traits<char> > (0B61310h)
00B61666 add esp,8
00B61669 mov ecx,eax
00B6166B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6B0B4h)]
00B61671 mov ecx,eax
00B61673 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6B0B8h)]
int asfasd=0;
}
00B61679 xor eax,eax
00B6167B ret
doIt(a,b)
经常被调用(每秒> 60000)。
我很好奇if(BoolT<i>::op)
是否可以优化。
如果条件不适合管道计算。
这是我的程序行为,可能会有所帮助:-
BoolT<2>::op
的访问-写入始终仅发生在程序的开头(例如A_pj2_UserDefine_Reg()
)。 BoolT<2>::op
(直接和间接)几乎总是只在项目pj2
中发生。 A_pj2_main.cpp
外,还有许多.cpp
调用pj1
的{{1}}。这是包含图:-B_pj1.h
doIt<>()
B_pj2_UserDefine.cpp
#pragma once
template<int i> bool op(){return true;}
template<int i> int doIt(int a,int b){
if(op<i>()){
return a+b;
}else{ return a-b;}
// return a+b;
}
B_pj2_main.cpp
#include "B_pj1.h"
template<> bool op<2>(){return false;}
该程序格式错误。 (参考:Storing C++ template function definitions in a .CPP file)
错误LNK2005“ bool __cdecl op <2>(void)”(?? $ op @ $ 01 @@ YA_NXZ)已经 在B_pj2_UserDefine.obj
中定义
答案 0 :(得分:1)
您当前的代码很好。如果条件每次都采用不同的分支,则仅会影响管线计算。当该标志仅写入一次时,分支预测会做得很好。
无论如何,常见的解决方案是C预处理器宏。从技术上讲可以解决if constexpr
,但实际上这需要库#include
从用户项目中获取某些东西,并且大多数库作者都不希望支持这种用例。
如果您愿意只使用库标头,也可以使用模板来解决,但更为复杂。由于以下原因,我尝试尽可能避免跨库的模板。编译时间开销。
答案 1 :(得分:0)
我认为示例被简化得太多,以至于看不到意图。因此,让我假设您有充分的理由这样做。
首先,请确保您在MSVC上进行了优化,将在命令行中添加/ O2(或类似名称)。
第二,测量。 CPU中的分支预测变量确实非常有效,您甚至可能不会注意到。
也就是说,当编译器看到if (false)
并且没有对其进行优化时,您可以认为它是编译器中的错误。不断折叠,尤其是在使用SSA作为模型时,是如此琐碎,以至于那些分支应该很快消失。
如果有疑问或想要在编译时强制使用,则使用if constexpr
是解决方案。它确实需要C ++ 17,并且可以在MSVC2017中使用。
另一种选择是使函数constexpr,尽管看起来像这样可能并不总是可能的:
template<int i> constexpr int doIt(int a,int b){
if(BoolT<i>::op){
return a+b;
}else{ return a-b;}
}
您可以这样称呼它:
constexpr int s1=doIt<1>(3,2);
int s2=doIt<2>(3,i);
这样,您的编译器需要在编译时计算s1的值。尽管它可以在运行时重用该函数来计算s2。 (请注意我正在传递变量)
关于链接器错误,C ++中有一个ODR(一个定义规则)。您可以通过添加内联来解决它,但是,到处都应该有相同的实现!我不建议将实现放在CPP文件中,因为它是UB的收据。