c++ - What do compilers do with compile-time branching? -


edit: took "if/else" case example can resolved @ compile time (eg when static values involved, cf <type_traits>). adapting answers below other types of static branching (eg, multiple branches or multi-criteria branches) should straightforward. note compile-time branching using template-meta programming not topic here.


in typical code this

#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     } } 

will compiler optimize if/else statement out when define specific template types later on in code?

a simple alternative write 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 difference in terms of performance between these solutions? there non-subjective grounds saying 1 better other? there other (possibly better) solutions deal compile-time branching?

tl;dr

there several ways different run-time behavior dependent on template parameter. performance should not primary concern here, flexibility , maintainability should. in cases, various thin wrappers , constant conditional expressions optimized away on decent compiler release builds. below small summary various tradeoffs (inspired this answer @andyprowl).

run-time if

your first solution simple run-time if:

template<class t> t numeric_procedure(const t& x) {     if (std::is_integral<t>::value) {         // valid code integral types     } else {         // valid code non-integral types,         // must compile integral types     } } 

it simple , effective: decent compiler optimize away dead branch.

there several disadvantages:

  • on platforms (msvc), constant conditional expression yields spurious compiler warning need ignore or silence.
  • but worse, on conforming platforms, both branches of if/else statement need compile types t, if 1 of branches known not taken. if t contains different member types depending on nature, compiler error try access them.

tag dispatching

your second approach known tag-dispatching:

template<class t> t numeric_procedure_impl(const t& x, std::false_type) {     // valid code non-integral types,     // can contain code invalid integral types }      template<class t> t numeric_procedure_impl(const t& x, std::true_type) {     // valid code integral types }  template<class t> t numeric_procedure(const t& x) {     return numeric_procedure_impl(x, std::is_integral<t>()); } 

it works fine, without run-time overhead: temporary std::is_integral<t>() , call one-line helper function both optimized way on decent platform.

the main (minor imo) disadvantage have boilerplate 3 instead of 1 function.

sfinae

closely related tag-dispatching sfinae (substitution failure not error)

template<class t, class = typename std::enable_if<!std::is_integral<t>::value>::type> t numeric_procedure(const t& x) {     // valid code non-integral types,     // can contain code invalid integral types }      template<class t, class = typename std::enable_if<std::is_integral<t>::value>::type> t numeric_procedure(const t& x) {     // valid code integral types } 

this has same effect tag-dispatching works differently. instead of using argument-deduction select proper helper overload, directly manipulates overload set main function.

the disadvantage can fragile , tricky way if don't know entire overload set (e.g. template heavy code, adl pull in more overloads associated namespaces didn't think of). , compared tag-dispatching, selection based on other binary decision lot more involved.

partial specialization

another approach use class template helper function application operator , 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 non-integral types,         // can contain code invalid integral types     } };  template<class t> struct numeric_functor<t, true> {     t operator()(t const& x) const     {         // valid code integral types     } };  template<class t> t numeric_procedure(t const& x) {     return numeric_functor<t, std::is_integral<t>::value>()(x); } 

this flexible approach if want have fine-grained control , minimal code duplication (e.g. if want specialize on size and/or alignment, floating point types). pattern matching given partial template specialization ideally suited such advanced problems. tag-dispatching, helper functors optimized away decent compiler.

the main disadvantage larger boiler-plate if want specialize on single binary condition.

if constexpr (c++1z proposal)

this reboot of failed earlier proposals static if (which used in d programming language)

template<class t> t numeric_procedure(const t& x) {     if constexpr (std::is_integral<t>::value) {         // valid code integral types     } else {         // valid code non-integral types,         // can contain code invalid integral types     } } 

as run-time if, in 1 place, main advantage here else branch dropped entirely compiler when known not taken. great advantage keep code local, , not have use little helper functions in tag dispatching or partial template specialization.

concepts-lite (c++1z proposal)

concepts-lite upcoming technical specification scheduled part of next major c++ release (c++1z, z==7 best guess).

template<non_integral t> t numeric_procedure(const t& x) {     // valid code non-integral types,     // can contain code invalid integral types }      template<integral t> t numeric_procedure(const t& x) {     // valid code integral types } 

this approach replaces class or typename keyword inside template< > brackets concept name describing family of types code supposed work for. can seen generalization of tag-dispatching , sfinae techniques. compilers (gcc, clang) have experimental support feature. lite adjective referring failed concepts c++11 proposal.


Comments

Popular posts from this blog

javascript - RequestAnimationFrame not working when exiting fullscreen switching space on Safari -

Python ctypes access violation with const pointer arguments -