если constexpr - почему полностью проверяется исключенный оператор?

Я возился с c ++ 20 consteval в GCC 10 и написал этот код

#include <optional>
#include <tuple>
#include <iostream>

template <std::size_t N, typename Predicate, typename Tuple>
consteval std::optional<std::size_t> find_if_impl(Predicate&& pred,
                                                  Tuple&& t) noexcept {
  constexpr std::size_t I = std::tuple_size_v<std::decay_t<decltype(t)>> - N;

  if constexpr (N == 0u) {
    return std::nullopt;
  } else {
    return pred(std::get<I>(t))
               ? std::make_optional(I)
               : find_if_impl<N - 1u>(std::forward<decltype(pred)>(pred),
                                      std::forward<decltype(t)>(t));
  }
}

template <typename Predicate, typename Tuple>
consteval std::optional<std::size_t> find_if(Predicate&& pred,
                                             Tuple&& t) noexcept {
  return find_if_impl<std::tuple_size_v<std::decay_t<decltype(t)>>>(
      std::forward<decltype(pred)>(pred), std::forward<decltype(t)>(t));
}

constexpr auto is_integral = [](auto&& x) noexcept {
    return std::is_integral_v<std::decay_t<decltype(x)>>;
};


int main() {
    auto t0 = std::make_tuple(9, 1.f, 2.f);
    constexpr auto i = find_if(is_integral, t0);
    if constexpr(i.has_value()) {
        std::cout << std::get<i.value()>(t0) << std::endl;
    }
}

Предполагается, что он работает как алгоритм поиска STL, но для кортежей и вместо возврата итератора он возвращает необязательный индекс на основе предиката времени компиляции. Теперь этот код отлично компилируется и распечатывает

9

Но если кортеж не содержит элемента интегрального типа, программа не компилируется, потому что i.value () по-прежнему вызывается для пустого необязательного элемента. Почему это так?


person Yamahari    schedule 18.12.2019    source источник
comment
меньший пример   -  person Artyer    schedule 18.12.2019
comment
@AndyG это не исправляет, не так ли? Икс)   -  person Yamahari    schedule 18.12.2019


Ответы (1)


Именно так работает constexpr if. Если мы проверим [stmt.if] / 2

Если оператор if имеет форму if constexpr, значение условия должно быть контекстно преобразованным константным выражением типа bool; эта форма называется оператором constexpr if. Если значение преобразованного условия ложно, первая подгруппа представляет собой отвергнутый оператор, в противном случае вторая подгруппа, если она присутствует, является отвергнутым оператором. Во время создания экземпляра включающего шаблонного объекта ([temp.pre]), если условие не зависит от значения после его создания, отброшенное подзаполнение (если оно есть) не создается. [... ]

акцент мой

Таким образом, мы можем видеть, что мы не оцениваем отброшенное выражение, только если мы находимся в шаблоне и если условие зависит от значения. main не является шаблоном функции, поэтому тело оператора if все еще проверяется компилятором на правильность.

Cppreference также говорит об этом в своем разделе о constexpr, если с:

Если оператор constexpr if появляется внутри шаблонной сущности и если условие не зависит от значения после создания экземпляра, отвергнутый оператор не создается при создании экземпляра включающего шаблона.

template<typename T, typename ... Rest>
void g(T&& p, Rest&& ...rs) {
    // ... handle p
    if constexpr (sizeof...(rs) > 0)
        g(rs...); // never instantiated with an empty argument list.
}

Вне шаблона полностью проверяется отклоненная инструкция. если constexpr не заменяет директиву предварительной обработки #if:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}
person NathanOliver    schedule 18.12.2019
comment
вы знаете причину этого? похоже, это было бы хорошо, если constexpr. Также решение будет, например, как-то обернуть его в шаблон? - person Yamahari; 18.12.2019
comment
@Yamahari Потому что шаблоны C ++ более и менее структурированы, чем вы хотите. И да, заверните его в шаблон (или напишите как i.value_or(0)) - person Barry; 18.12.2019
comment
@Yamahari Да, решением было бы поместить код в шаблон функции. Что касается рассуждений, я не знаю почему. Это, наверное, хороший вопрос. - person NathanOliver; 18.12.2019
comment
@Barry value_or (0) отлично работает, но для случая, когда кортеж имеет размер 0 - person Yamahari; 18.12.2019