4

Consider a function template func that is very performance critical. It can be instantiated with T=Type1 or some other type. Part of the function logic depends on T it is instantiated with.

One can either explicitly use a if constexpr (Code B) or use a vanilla if instead (Code A), while compiler probably optimizes the code.

However, I wonder, how the implementation without constexpr (Code A) is any different? Isn't the compiler capable of detecting which branch of if (in Code A) to use at compile time while instantiating? Can it still (for Code A) generate a less efficient code?

Code A. Without if constexpr:

template<class T>
void func(T argument)
{
    // some general type-independent logic
    if (std::is_same<Type1,T>::value)
    {
        // do something
    }
    else
    {
        // do something else
    }
    // some general type-independent logic
}

Code B. With if constexpr:

template<class T>
void func(T argument)
{
    // some general type-independent logic
    if constexpr (std::is_same<Type1,T>::value)
    {
        // do something
    }
    else
    {
        // do something else
    }
    // some general type-independent logic
}

Both codes A & B compile, as do something and do something else are well-formed for any T.

There are some similar-sounding questions:

The aforementioned questions do not answer if Code B is preferable to Code A for some reason (when both branches are well-formed anyway).

The only advantage I see would be to tell the programmer explicitly that this if is compile-time; however, I would say the conditional expression is self-explanatory.

Barry
  • 286,269
  • 29
  • 621
  • 977
Anton Menshov
  • 2,266
  • 14
  • 34
  • 55
  • Primarily opinion based. There are arguments for either Code B being "better" than Code A, or Code A being "better" than code B. It all depends on the specific, and precise, definition of what "better" means. The only time you can objectively state that "B" is better than "A" is when "A" results in undefined behavior, and "B" is not. Everything else is up for grabs. – Sam Varshavchik Feb 06 '19 at 01:57
  • With B there will be no branch in the compiled code, so you've got that going for you. Which is nice. – NathanOliver Feb 06 '19 at 02:12
  • For the same reason the `override` keyword exists, it's desirable to actually have a compilable, but discarded branch that serves as a canary should something else, in some other header file gets changed that would result in the discarded branch no longer compiling, and raising an alert as to the issue at hand. – Sam Varshavchik Feb 06 '19 at 02:26
  • 1
    @SamVarshavchik The `override` [contextual] keyword exists because it's very easy to _hide_ when you intended to _override_ by simply getting some part of the signature wrong. I have no idea what benefit you're talking about in the rest of that comment? Mind you, the motivation of `if constexpr` is very much that the discarded branch _does not_ and _cannot_ compile - and we're trying to avoid that. – Barry Feb 06 '19 at 02:40
  • 1
    I cannot explain this any better without giving a long example, but sometimes you /do/ want a compilation error if, as a result of some change that's /not/ in the immediate vicinity, the aforementioned "immediate vicinity" no longer compiles, instead of being suppressed inside a `if constexpr`. Suffice to say, that the capsule summary is that one way to avoid runtime bugs is to make arrangements to (to the maximum extent possible) turn runtime bugs into compilation failures. – Sam Varshavchik Feb 06 '19 at 02:51

2 Answers2

11

if constexpr is not intended about optimization. Compilers are very good at optimizing away a branch that is if (true) or if (false) (since we're talking about constant expressions, that is what it boils down to). Here is a godbolt demo of the example in OP - you'll note that both gcc and clang, even on -O0, do not emit a branch for a simple if.

if constexpr is all about ensuring that only one branch of the if is instantiated. This is hugely important and valuable for writing templates - because now we can actually write conditionally compiling code within the body of the same function instead of writing multiple artificial functions just to avoid instantiation.

That said, if you have a condition that is a known constant expression - just always use if constexpr, whether or not you need the instantiation benefit. There is no downside to such a decision. It makes it clearer to readers that indeed this condition is constant (since otherwise it wouldn't even compile). It will also force the evaluation of the expression as a constant (a slight variant leads gcc to emit a branch at -O0, thought not at -O1), which with the coming addition of is_constant_evaluated() may become more important in the long run (possibly even negating my opening paragraph).


The only advantage I see would be to tell the programmer explicitly that this if is compile-time; however, I would say the conditional expression is self-explanatory.

To address this specifically, yes, std::is_same<X, Y>::value is "self-explanatory" that it is a constant expression... because we happen to be familiar with std::is_same. But it's less obvious whether foo<X>::value is a constant expression or whether foo<X>() + bar<Y>() is a constant expression or anything more arbitrarily complicated than that.

It's seeing if constexpr that makes the fact that it's compile-time self-explanatory, not the content of the condition itself.

Barry
  • 286,269
  • 29
  • 621
  • 977
1

Adding an example to @Barry 's explanation: The use is primarily for writing templates. Consider the following:

template <class T>
auto get_value() 
{
    if constexpr (std::is_same_v<T, int>) {
        return 1
    } else {
        return 2.0;
    }
}

You can note that, if the template parameter is int, the return value is determined to be int, while it is float when the template parameter is not int. You will see that this does not work with non-constexpr if statements, because at instantiation, all returns of a function must have a common type, which the former does not have. The only other way of achieving this is to use c++20 contraints, or std::enable_if to overload the function based on the template parameter.

Hunter Kohler
  • 1,885
  • 1
  • 18
  • 23