Nested constexpr-if statement in discarded branch is still evaluated?

  • A+
Category:Languages

It appears to me that a constexpr-if statement that is inside the discarded branch of another constexpr-if statement is evaluated in MSVC (version 15.7.3).

Consider the following code:

#include <tuple> #include <type_traits>  template <size_t I> int test() {     if constexpr(I != 0) {         return 0;     }     else { // This branch is discarded, but it seems that the constexpr-if below is still evaulated?         if constexpr(std::is_same_v<int, std::tuple_element_t<I, std::tuple<int>>>) { // some constexpr check that is valid only when I == 0             return 1;         }         else {             return 2;         }     } }  int main() {     test<1>();     return 0; } 

The code above fails to compile in MSVC, because std::tuple_element_t will fail a static assertion when I exceeds the bounds of the tuple. This suggests that somehow the code in the discarded branch was evaluated as well, even though it is dependent on the template parameter I.

According to cppreference, constexpr-if requires that "the discarded statement can't be ill-formed for every possible specialization", but I'm having a hard time deciding if this is the case here.

It also seems that GCC and Clang accepts this code without issue (tested on Compiler Explorer).

Is the compilation error acceptable by the C++ standard, or is MSVC non-compliant here?

(Also, if what I'm expecting the code to do isn't guaranteed by the standard, is there another way to accomplish nested constexpr-if statements?)

 


gcc and clang are right. It would be very counter-intuitive if the only statement in a discarded branch that gets not discarded is another if constexpr statement.

[stmt.if]p2 doesn't mention anything about that:

If the value of the converted condition is false, 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 (Clause 17), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.

Emphasis mine. The standard says that the discarded statement is not instantiated, which in your case is the else { /*... * }. Anything in that branch is not instantiated, so the compiler is not allowed to instantiate anything, and so MSVC is wrong by instantiating std::tuple_element<I, std::tuple<int>>.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: