Reputation: 954
There seems to be a lot of confusion out there about if constexpr
and the difference between dependent- and non-dependent expressions, particularly in the context of static_assert
. Before CWG2518, static_assert( false )
required workarounds such as listed in the code below to make the assertion condition dependent on template parameter(s).
In general, I consider an expression dependent if it depends on template parameter(s), even if the expression's final value does not depend on the template parameter(s). For example, in static_assert( (sizeof(T), false), "Type T is invalid" )
, sub-expression sizeof(T)
makes the comma expression dependent even though it doesn't affect the value of the comma expression and cannot have any side effects.
However, in the example code below, it compiles just fine even though the code static_assert( make_dependent<I>(false), "Invalid value" )
can be statically determined to be a template argument mismatch, because template parameter I
is always a non-type template parameter and make_dependent
only accepts type template arguments, and no set of template arguments can falsify this mismatch. But if this if constexpr
block is selected, then instead of the static_assert
failure, there will be a template argument mismatch.
It seems like because dependent name I
appears anywhere in the expression, it is deferred until template instantiation time, and even at template instantiation time, if the if constexpr
does not select this block, the always-mismatching template argument is still accepted, even though no set of template arguments can falsify this mismatch.
Is it true that an unselected block of an if constexpr
is totally ignored at template instantiation time, and that only syntax errors, and semantic errors in non-dependent expressions, are diagnosed at template definition time, even if the dependent expressions can be statically determined to have diagnosable semantic errors regardless of template arguments?
#include <utility>
// Make an expression dependent on arbitrary template type parameters
// Can be used to defer using a type which is incomplete until template instantiation time
// Can be used to defer static_assert( false, ... ) until template instantiation time
template<typename..., typename T>
constexpr T&& make_dependent( T&& x ) {
return std::forward<T>( x );
}
struct incomplete;
template<typename T = void>
void defer_incomplete( incomplete& i ) {
// i.method(); Error: cannot call method on incomplete type
make_dependent<T>( i ).method(); // Okay -- defer until template instantiation
}
template<int I>
auto func() {
if constexpr( I != 0 ) {
return I;
} else {
// Fails before C++23 because static_assert condition is non-dependent
// https://cplusplus.github.io/CWG/issues/2518.html
// static_assert( false, "Invalid value" );
// Uses always-false dependent static_assert condition
static_assert( I != 0, "Invalid value" );
// Cleverly uses comma operator to make static_assert condition dependent
static_assert( ( I, false ), "Invalid value" );
// Works even though the make_dependent template argument is always a non-type
// Fails with template argument mismatch only if if constexpr branch reaches here
static_assert( make_dependent<I>( false ), "Invalid value" );
}
}
struct incomplete {
void method() {}
};
int main() {
incomplete i;
defer_incomplete( i );
func<1>();
}
Upvotes: 2
Views: 65