if constexpr vs sfinae
Can one assume that the compiler, on evaluating the
if constexpr
completely discards the non-verified condition and generate code only for the branch which satisfy theif constexpr
condition? Does the standard specifies such a behavior for the compiler?
The standard specifies that, from [stmt.if]:
If the
if
statement is of the formif constexpr
, the value of the condition shall be a contextually converted constant expression of typebool
; this form is called a constexpr if statement. If the value of the converted condition isfalse
, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity, if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
The point here is that the discard statement is not instantiated - this is the whole purpose behind if constexpr
as a language feature, to allow you to write:
template <typename T0, typename... T>
void print_ifconstexpr(T0&& x, T&&... rest)
{
std::cout << x << std::endl;
if constexpr (sizeof...(T) > 0) {
print_ifconstexpr(std::forward<T>(rest)...);
}
}
You can't do that with a simple if
, because that would still require instantiating the substatements - even if the condition can be determined to be false
at compile time. A simple if
would require the ability to call print_ifconstexpr()
.
if constexpr
will not instantiate the recursive call unless there is something in rest...
, so this works.
Everything else follows from the lack of instantiation. There can't be any generated code for the discarded statement.
The if constexpr
form is easier to write, easier to understand, and surely compiles faster. Definitely prefer it.
Note that your first example doesn't need the SFINAE at all. This works just fine:
template <typename T>
void print(T&& x)
{
std::cout << x << std::endl;
}
template <typename T0, typename... T>
void print(T0&& x, T&&... rest)
{
std::cout << x << std::endl;
print(std::forward<T>(rest)...);
}
As does:
void print() { }
template <typename T0, typename... T>
void print(T0&& x, T&&... rest)
{
std::cout << x << std::endl;
print(std::forward<T>(rest)...);
}