问题描述
编辑:我以if / else为例,有时可以在编译时解决(例如,当涉及静态值时,cf < type_traits> ;
)。将以下答案调整为其他类型的静态分支(例如,多个分支或多标准分支)应该是直截了当的。请注意,使用模板元编程的编译时分支不是此处的主题。
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).
您的第一个解决方案是简单的运行时如果
:
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< T>()
并且对单线辅助函数的调用都将在任何体面的平台上进行优化。
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)缺点是你有一些样板而不是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
}
这与tag-dispatching具有相同的效果,但工作方式略有不同。它不是使用参数推导来选择正确的帮助器重载,而是直接操作主函数的重载集。
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.
如果你只想专注于a,那么主要的缺点是稍大的锅炉板单个二元条件。
The main disadvantage is the slightly larger boiler-plate if you only want to specialize on a single binary condition.
这是早期的静态建议
(用于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
}
}
与你的运行时如果
一样,一切都在一个地方,但是这里的主要优点是编译器在知道不被采用时将完全删除 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,<$ c)的一部分$ c> 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
}
此方法替换<$内的类
或 typename
关键字C $ C>模板< > 括号,其概念名称描述了代码应该用于的类型系列。它可以看作是标签调度和SFINAE技术的概括。一些编译器(gcc,Clang)对此功能有实验支持。 Lite形容词指的是失败的Concepts C ++ 11提案。
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.
这篇关于编译器对编译时分支做了什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!