問題描述
我以if/else"情況為例,有時可以在編譯時解決(例如,當涉及靜態值時,參見
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
}
}
當我稍后在代碼中定義特定模板類型時,編譯器會優化 if/else 語句嗎?
will the compiler optimize the if/else statement out when I define specific template types later on in my code?
一個簡單的替代方法是編寫如下內容:
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
}
這些解決方案在性能方面是否存在差異?是否有任何非主觀理由可以說一個比另一個好?是否有其他(可能更好)的解決方案來處理編譯時分支?
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
有幾種方法可以根據模板參數獲得不同的運行時行為.在這里,性能不應該是您的主要關注點,而應該是靈活性和可維護性.在所有情況下,各種瘦包裝器和常量條件表達式都將在任何合適的編譯器上進行優化以用于發布版本.下面是各種權衡的小總結(靈感來自@AndyProwl 的這個答案).
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).
您的第一個解決方案是簡單的運行時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
}
}
它既簡單又有效:任何合適的編譯器都會優化掉死分支.
It is simple and effective: any decent compiler will optimize away the dead branch.
有幾個缺點:
- 在某些平臺 (MSVC) 上,常量條件表達式會產生虛假的編譯器警告,然后您需要忽略或忽略該警告.
- 但更糟糕的是,在所有符合標準的平臺上,
if/else
語句的兩個分支都需要為所有類型T
實際編譯,即使如果其中一個分支已知不被采用.如果T
根據其性質包含不同的成員類型,那么您將在嘗試訪問它們時立即收到編譯器錯誤.
- 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.
您的第二種方法稱為標記調度:
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>());
}
它工作正常,沒有運行時開銷:臨時 std::is_integral
和對單行輔助函數的調用都將在任何體面的平臺上進行優化.
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)缺點是您有一些帶有 3 個而不是 1 個函數的樣板.
The main (minor IMO) disadvantage is that you have some boilerplate with 3 instead of 1 function.
與標簽調度密切相關的是 SFINAE(替換失敗不是錯誤)
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
}
這與標簽調度具有相同的效果,但工作方式略有不同.它不是使用參數推導來選擇合適的輔助重載,而是直接操作主函數的重載集.
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.
缺點是,如果您不確切知道整個重載集是什么(例如,使用模板繁重的代碼,ADL 可能會從關聯的命名空間中引入更多的重載)不考慮).與標簽分派相比,基于二元決策以外的任何選擇的選擇要復雜得多.
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.
另一種方法是使用帶有函數應用運算符的類模板助手并對其進行部分特化
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);
}
如果您想進行細粒度控制和最少的代碼重復(例如,如果您還想專注于大小和/或對齊,但僅針對浮點類型),這可能是最靈活的方法.部分模板特化給出的模式匹配非常適合此類高級問題.與標記分派一樣,任何合適的編譯器都會優化輔助函子.
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.
如果您只想專注于單個二進制條件,主要缺點是樣板文件稍大.
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
}
}
與您的運行時 if
一樣,所有內容都在一個地方,但這里的主要優點是 else
分支將被編譯器完全刪除已知不會被采取.一個很大的優勢是您可以將所有代碼保留在本地,并且不必像在標簽分派或部分模板特化中那樣使用很少的輔助函數.
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 是一個 即將推出的技術規范,計劃成為下一個主要 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
帶有概念名稱的括號,描述代碼應該適用的類型系列.它可以看作是標簽調度和 SFINAE 技術的概括.一些編譯器(gcc、Clang)對此功能有實驗性支持.Lite 形容詞指的是失敗的 Concepts C++11 提案.class
或 typename
關鍵字.>
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.
這篇關于編譯器如何處理編譯時分支?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!