编辑:我以“if / else”情况为例,该情况有时可以在编译时解决(例如,当涉及静态值时,请参见<type_traits>)。使下面的答案适应其他类型的静态分支(例如,多个分支或多条件分支)应该很简单。注意,这里不是使用模板元编程的编译时分支。

在这样的典型代码中

#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语句吗?

一个简单的替代方法是编写如下代码:
#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
}

这些解决方案之间在性能方面有区别吗?有没有非主观的理由说一个比另一个更好?还有其他(可能更好)的解决方案来处理编译时分支吗?

最佳答案

TL; DR

有几种方法可以根据模板参数获得不同的运行时行为。 在这里,性能不是您的主要关注点,但灵活性和可维护性应该是您关注的重点。 在所有情况下,各种精简包装和常量条件表达式都将在任何适用的发行版编译器上进行优化。下面是有关各种折衷的小结(由@AndyProwl的this answer启发)。

运行时

您的第一个解决方案是简单的运行时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
    }
}

它简单而有效:任何合适的编译器都可以优化无效分支。

有几个缺点:

在某些平台(MSVC)上使用
  • ,常量条件表达式会产生虚假的编译器警告,然后您需要忽略该警告或使其保持静默状态。
  • 但是,更糟糕的是,在所有兼容平台上,if/else语句的两个分支实际上都需要针对所有类型T 进行编译,即使已知其中一个分支都不采用。如果T根据其性质包含不同的成员类型,则在尝试访问它们时会出现编译器错误。

  • 标签派发

    您的第二种方法称为标记分派(dispatch):
    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>()和对单行辅助函数的调用都可以在任何合适的平台上进行优化。

    主要(较小的IMO)缺点是您的某些样板具有3个功能而不是1个功能。

    FINEA

    与标记分派(dispatch)密切相关的是SFINAE(替换失败不是错误)
    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
    }
    

    这与标记分派(dispatch)具有相同的效果,但工作原理略有不同。它不使用参数推论来选择适当的帮助程序重载,而是直接为您的main函数操纵重载集。

    缺点是,如果您不确切知道整个重载集是什么,它可能是一种脆弱且棘手的方法(例如,使用大量模板代码, ADL可能会从您没有想到的关联 namespace 中引入更多重载) 。与标记分派(dispatch)相比,基于二进制决策以外的任何选择都涉及更多。

    部分特化

    另一种方法是将类模板帮助程序与函数应用程序运算符一起使用,并对其进行部分特化处理
    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);
    }
    

    如果您想拥有细粒度的控制和最少的代码重复(例如,如果您还想专门研究大小和/或对齐方式,但只说浮点类型的话),这可能是最灵活的方法。通过部分模板特化提供的模式匹配非常适合此类高级问题。与标记分派(dispatch)一样,辅助函子可以通过任何不错的编译器进行优化。

    主要缺点是,如果您只想专门研究单个二进制条件,则样板会稍大一些。

    如果是constexpr(C++ 1z建议)

    这是static if失败的早期建议的reboot(在D编程语言中使用)
    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分支。一个很大的优点是您可以将所有代码都保留在本地,而不必像标签分派(dispatch)或部分模板专门化那样使用少量的辅助函数。

    Concepts-Lite(C++ 1z建议)

    Concepts-Lite是upcoming Technical Specification,已计划成为下一个主要C++版本(C++ 1z,其中z==7为最佳猜测)的一部分。
    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
    }
    

    此方法用描述名称应用于代码的类型族的概念名称替换class括号内的typenametemplate< >关键字。可以将其视为标记分派(dispatch)和SFINAE技术的概括。一些编译器(gcc,Clang)对此功能提供实验性支持。 Lite形容词指的是失败的Concepts C++ 11建议。

    关于c++ - 编译器对编译时分支有什么作用?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/46950940/

    10-17 01:36