编译器如何处理编译时分支?
我以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.
相关文章