Вложенная инструкция constexpr-if в отброшенной ветке еще оценивается?

Мне кажется, что оператор constexpr-if, который находится внутри отброшенной ветки другого оператора constexpr-if, вычисляется в MSVC (версия 15.7.3).

Рассмотрим следующий код:

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

Приведенный выше код не может скомпилироваться в MSVC, потому что std::tuple_element_t не сможет std::tuple_element_t статическое утверждение, если I std::tuple_element_t границы кортежа. Это говорит о том, что каким-то образом был оценен код в отброшенной ветки, даже если он зависит от параметра шаблона I

Согласно cppreference, constexpr-if требует, чтобы "отброшенное утверждение не могло быть плохо сформировано для любой возможной специализации", но мне сложно решить, имеет ли это место здесь.

Также кажется, что GCC и Clang принимают этот код без проблем (тестируются в Compiler Explorer).

Является ли ошибка компиляции приемлемой стандартом C++, или MSVC несовместим здесь?

(Кроме того, если то, что я ожидаю от кода, не гарантируется стандартом, есть ли другой способ выполнения вложенных операторов constexpr-if?)

Ответ 1

gcc и clang являются правильными. Было бы очень контринтуитивно, если единственный оператор в отброшенной ветки, который не отбрасывается, является другим, if constexpr statement.

[stmt.if] p2 ничего не говорит об этом:

Если значение преобразованного условия является ложным, первое подзадача является отброшенным оператором, иначе второе подчинение, если оно присутствует, является отброшенным оператором. Во время создания закрытого шаблонного объекта (раздел 17), если условие не зависит от стоимости после его создания, отброшенное подзадача (если оно есть) не создается.

Акцент мой. В стандарте говорится, что отброшенный оператор не создается, что в вашем случае является else {/*... * }. Ничего в этой ветки не создается, поэтому компилятору не разрешается создавать какие-либо экземпляры, поэтому MSVC ошибочен, std::tuple_element<I, std::tuple<int>> экземпляр std::tuple_element<I, std::tuple<int>>.