問題描述
我以if/else"情況為例,有時(shí)可以在編譯時(shí)解決(例如,當(dāng)涉及靜態(tài)值時(shí),參見
I took the "if/else" case as an example that can sometimes be resolved at compile time (eg when static values are involved, cf <type_traits>
). Adapting the answers below to other types of static branching (eg, multiple branches or multi-criteria branches) should be straightforward. Note that compile-time branching using template-meta programming is not the topic here.
在這樣的典型代碼中
#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
}
}
當(dāng)我稍后在代碼中定義特定模板類型時(shí),編譯器會優(yōu)化 if/else 語句嗎?
will the compiler optimize the if/else statement out when I define specific template types later on in my code?
一個(gè)簡單的替代方法是編寫如下內(nèi)容:
A simple alternative would be to write something like this:
#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
}
這些解決方案在性能方面是否存在差異?是否有任何非主觀理由可以說一個(gè)比另一個(gè)好?是否有其他(可能更好)的解決方案來處理編譯時(shí)分支?
Is there a difference in terms of performance between these solutions? Is there any non-subjective grounds for saying that one is better than the other? Are there other (possibly better) solutions to deal with compile-time branching?
推薦答案
TL;DR
有幾種方法可以根據(jù)模板參數(shù)獲得不同的運(yùn)行時(shí)行為.在這里,性能不應(yīng)該是您的主要關(guān)注點(diǎn),而應(yīng)該是靈活性和可維護(hù)性.在所有情況下,各種瘦包裝器和常量條件表達(dá)式都將在任何合適的編譯器上進(jìn)行優(yōu)化以用于發(fā)布版本.下面是各種權(quán)衡的小總結(jié)(靈感來自@AndyProwl 的這個(gè)答案).
TL;DR
There are several ways to get different run-time behavior dependent on a template parameter. Performance should not be your primary concern here, but flexibility and maintainability should. In all cases, the various thin wrappers and constant conditional expressions will all be optimized away on any decent compiler for release builds. Below a small summary with the various tradeoffs (inspired by this answer by @AndyProwl).
您的第一個(gè)解決方案是簡單的運(yùn)行時(shí)if
:
Your first solution is the simple run-time 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
}
}
它既簡單又有效:任何合適的編譯器都會優(yōu)化掉死分支.
It is simple and effective: any decent compiler will optimize away the dead branch.
有幾個(gè)缺點(diǎn):
- 在某些平臺 (MSVC) 上,常量條件表達(dá)式會產(chǎn)生虛假的編譯器警告,然后您需要忽略或忽略該警告.
- 但更糟糕的是,在所有符合標(biāo)準(zhǔn)的平臺上,
if/else
語句的兩個(gè)分支都需要為所有類型T
實(shí)際編譯,即使如果其中一個(gè)分支已知不被采用.如果T
根據(jù)其性質(zhì)包含不同的成員類型,那么您將在嘗試訪問它們時(shí)立即收到編譯器錯(cuò)誤.
- on some platforms (MSVC), a constant conditional expression yields a spurious compiler warning which you then need to ignore or silence.
- But worse, on all conforming platforms, both branches of the
if/else
statement need to actually compile for all typesT
, even if one of the branches is known not to be taken. IfT
contains different member types depending on its nature, then you will get a compiler error as soon as you try to access them.
您的第二種方法稱為標(biāo)記調(diào)度:
Your second approach is known as tag-dispatching:
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>());
}
它工作正常,沒有運(yùn)行時(shí)開銷:臨時(shí) std::is_integral
和對單行輔助函數(shù)的調(diào)用都將在任何體面的平臺上進(jìn)行優(yōu)化.
It works fine, without run-time overhead: the temporary std::is_integral<T>()
and the call to the one-line helper function will both be optimized way on any decent platform.
主要(次要 IMO)缺點(diǎn)是您有一些帶有 3 個(gè)而不是 1 個(gè)函數(shù)的樣板.
The main (minor IMO) disadvantage is that you have some boilerplate with 3 instead of 1 function.
與標(biāo)簽調(diào)度密切相關(guān)的是 SFINAE(替換失敗不是錯(cuò)誤)
Closely related to tag-dispatching is SFINAE (Substitution failure is not an error)
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
}
這與標(biāo)簽調(diào)度具有相同的效果,但工作方式略有不同.它不是使用參數(shù)推導(dǎo)來選擇合適的輔助重載,而是直接操作主函數(shù)的重載集.
This has the same effect as tag-dispatching but works slightly differently. Instead of using argument-deduction to select the proper helper overload, it directly manipulates the overload set for your main function.
缺點(diǎn)是,如果您不確切知道整個(gè)重載集是什么(例如,使用模板繁重的代碼,ADL 可能會從關(guān)聯(lián)的命名空間中引入更多的重載)不考慮).與標(biāo)簽分派相比,基于二元決策以外的任何選擇的選擇要復(fù)雜得多.
The disadvantage is that it can be a fragile and tricky way if you don't know exactly what the entire overload set is (e.g. with template heavy code, ADL could pull in more overloads from associated namespaces you didn't think of). And compared to tag-dispatching, selection based on anything other than a binary decision is a lot more involved.
另一種方法是使用帶有函數(shù)應(yīng)用運(yùn)算符的類模板助手并對其進(jìn)行部分特化
Another approach is to use a class template helper with a function application operator and partially specialize it
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);
}
如果您想進(jìn)行細(xì)粒度控制和最少的代碼重復(fù)(例如,如果您還想專注于大小和/或?qū)R,但僅針對浮點(diǎn)類型),這可能是最靈活的方法.部分模板特化給出的模式匹配非常適合此類高級問題.與標(biāo)記分派一樣,任何合適的編譯器都會優(yōu)化輔助函子.
This is probably the most flexible approach if you want to have fine-grained control and minimal code duplication (e.g. if you also want to specialize on size and/or alignment, but say only for floating point types). The pattern matching given by partial template specialization is ideally suited for such advanced problems. As with tag-dispatching, the helper functors are optimized away by any decent compiler.
如果您只想專注于單個(gè)二進(jìn)制條件,主要缺點(diǎn)是樣板文件稍大.
The main disadvantage is the slightly larger boiler-plate if you only want to specialize on a single binary condition.
這是重啟 之前失敗的 static if
(在 D 編程語言中使用)
This is a reboot of failed earlier proposals for static if
(which is used in the D programming language)
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
}
}
與您的運(yùn)行時(shí) if
一樣,所有內(nèi)容都在一個(gè)地方,但這里的主要優(yōu)點(diǎn)是 else
分支將被編譯器完全刪除已知不會被采取.一個(gè)很大的優(yōu)勢是您可以將所有代碼保留在本地,并且不必像在標(biāo)簽分派或部分模板特化中那樣使用很少的輔助函數(shù).
As with your run-time if
, everything is in one place, but the main advantage here is that the else
branch will be dropped entirely by the compiler when it is known not to be taken. A great advantage is that you keep all code local, and do not have to use little helper functions as in tag dispatching or partial template specialization.
Concepts-Lite 是一個(gè) 即將推出的技術(shù)規(guī)范,計(jì)劃成為下一個(gè)主要 C++ 版本(C++1z,z==7
為最佳猜測)的一部分.
Concepts-Lite is an upcoming Technical Specification that is scheduled to be part of the next major C++ release (C++1z, with z==7
as the best guess).
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
帶有概念名稱的括號,描述代碼應(yīng)該適用的類型系列.它可以看作是標(biāo)簽調(diào)度和 SFINAE 技術(shù)的概括.一些編譯器(gcc、Clang)對此功能有實(shí)驗(yàn)性支持.Lite 形容詞指的是失敗的 Concepts C++11 提案.class
或 typename
關(guān)鍵字.>
This approach replaces the class
or typename
keyword inside the template< >
brackets with a concept name describing the family of types that the code is supposed to work for. It can be seen as a generalization of the tag-dispatching and SFINAE techniques. Some compilers (gcc, Clang) have experimental support for this feature. The Lite adjective is referring to the failed Concepts C++11 proposal.
這篇關(guān)于編譯器如何處理編譯時(shí)分支?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!