"Если constexpr" в C++17 не работает в не шаблонной функции

Я пытался играть со стандартом C++17. Я пытался использовать одну из функций C++17 if constexpr, И у меня возникла проблема... Пожалуйста, посмотрите на следующий код. Это компилируется без ошибок. В следующем коде я пытался использовать if constexpr чтобы проверить, если это указатель.

#include <iostream>
#include <type_traits>

template <typename T>
void print(T value)
{
  if constexpr (std::is_pointer_v<decltype(value)>)
    std::cout << "Ptr to " << *value << std::endl; // Ok
  else
    std::cout << "Ref to " << value << std::endl;
}

int main()
{
  auto n = 1000;
  print(n);
  print(&n);
}

Но когда я переписываю приведенный выше код, как показано ниже, где if constexpr находится в main функция:

#include <iostream>
#include <type_traits>

int main()
{
  auto value = 100;
  if constexpr (std::is_pointer_v<decltype(value)>)
    std::cout << "Ptr to " << *value << std::endl; // Error
  else
    std::cout << "Ref to " << value << std::endl;
}

Я получаю ошибку компиляции:

main.cpp:8:32: error: invalid type argument of unary ‘*’ (have ‘int’) 
std::cout << "Ptr to " << *value << std::endl;

Проблема не в основной функции. Это может быть любая функция, аналогичная следующей.

void print()
{
  auto value = 100;
  if constexpr (std::is_pointer_v<decltype(value)>)
    std::cout << "Ptr to " << *value << std::endl; // Error
  else
    std::cout << "Ref to " << value << std::endl;
}

int main()
{
  print();
}

Я хотел бы знать, почему if constexpr работает только в шаблонных функциях, даже если тип определяется по типу decltype из входного параметра.

5 ответов

Я хотел бы знать, почему "if constexpr"работает только в шаблонных функциях, даже если тип определяется decltype из входного параметра.

Это по замыслу.

if constexpr не будет создавать ветку, которая не была взята, если она находится внутри шаблона. Он не просто будет относиться к ветке, которую не воспринимают как суп-лексем, и не будет разбирать ее или выполнять полностью семантический анализ. Обе стороны все еще будут анализироваться, и с тех пор *value плохо сформирован для ints, это ошибка.

Вы просто не можете использовать if constexpr чтобы избежать компиляции не шаблонного кода. Это только для того, чтобы избежать создания экземпляра кода шаблона, который потенциально недопустим для конкретной специализации.

Я хотел бы знать, почему работает только в шаблонных функциях, даже если тип выводится с помощью decltype из входного параметра.

Дело в том, что он также работает без шаблона, просто не так, как вы ожидаете.

Для того, чтобы работать так, как вы заявили, вам нужен не только шаблон, но и содержащиеся в нем выражения, которые должны зависеть от параметров шаблона.

Давайте шаг за шагом рассмотрим, почему это сделано именно так в C ++ и каковы последствия.

Начнем с простого. Компилируется ли следующий код?

      void func_a() {
    nonexistant();
}

Я думаю, мы все согласимся с тем, что он не будет компилироваться, мы собираемся использовать функцию, которая не была объявлена.

Добавим один слой.

Компилируется ли следующий код?

      template<typename T>
void func_b() {
    nonexistant();
}

С правильным компилятором это не скомпилируется.

Но почему так? Вы можете возразить, что этот код на самом деле никогда не компилируется, поскольку шаблон никогда не создается.

Стандарт определяет то, что они называют двухфазным поиском имени . Дело в том, что даже если шаблон не создан, компилятор должен выполнить поиск имени всего, что не зависит от параметра шаблона.

И в этом есть смысл. Если выражение не зависят от, почему его значение изменится с? Следовательно, это выражение такое же, как в в глазах компилятора.

Теперь войдите.

Чтобы такая конструкция хорошо работала с остальным языком, она определяется как ветка для создания экземпляров. Таким образом, мы можем сделать некоторый код без создания экземпляров, даже без шаблонов!

      extern int a;

void helper_1(int*);

void func_c() {
    if constexpr (false) {
        helper_1(&a);
    }
}

Ответ заключается в том, что ODR и не используются. Мы могли бы уйти а также не определено, и ошибок компоновщика не будет.

Более того, компилятор не будет создавать экземпляры шаблонов, которые находятся в отброшенной ветви:

      template<typename T>
void helper_2() {
    T::nonexistant();
}

void func_d() {
    if constexpr (false) {
        helper_2<int>();
    }
}

Этот код не будет компилироваться с обычным .

Как видите, отброшенная ветвь работы работает точно так же, как шаблон, который не был создан, даже в коде, отличном от шаблона.

А теперь перемешаем:

      template<typename T>
void func_b_2() {
    if constexpr (false) {
        nonexistant();
    }
}

Это похоже на нашу функцию шаблона в начале. Мы сказали, что даже если шаблон не был создан, код был недопустимым, поскольку недопустимое выражение не зависит от. Мы также сказали, что это ветвь в процессе создания экземпляра. Ошибка произошла до создания экземпляра. Этот код тоже не компилируется.

Итак, наконец, этот код тоже не компилируется:

      void func_e() {
    if constexpr (false) {
        nonexistant();
    }
}

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


Так в чем же польза? Почему вроде работает только в шаблонах?

Дело в том, что в шаблонах это не работает иначе. Так же, как мы видели с , ошибка все еще возникает.

Но этот случай будет работать:

      template<typename T>
void helper_3() {
    if constexpr (false) {
        T::nonexistant();
    }
}

void func_f() {
    helper_3<int>();
}

Выражение недействителен, но код компилируется. Это потому, что поскольку это выражение, зависящее от , поиск имени выполняется на втором этапе. Второй этап поиска имени выполняется во время создания экземпляра шаблона . Ветка, содержащая всегда отбрасывается, поэтому вторая фаза поиска имени никогда не выполняется.

Вот и все. не о том, чтобы не компилировать часть кода. Как и шаблон, они компилируются, и выполняется любое выражение, которое может выполнять поиск имени. касается управления созданием экземпляров , даже если это не шаблонная функция. Все правила, которые применяются к шаблонам, также применяются ко всем ветвям. Двухэтапный поиск имени по-прежнему применяется и позволяет программистам не создавать экземпляры некоторой части кода , которая в противном случае не была бы скомпилирована при создании экземпляра.

Поэтому, если код не может быть скомпилирован в шаблоне, для которого не создан экземпляр, он не будет компилироваться в ветви это не создается.

Стандарт C++, пункт 9.4.1:

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

(акцент мой)

Итак, подзаголовок constexpr if все еще создается, если он не находится внутри шаблона, поэтому он должен по крайней мере компилироваться.

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

Вот

Я немного опоздал на вечеринку, но хороший трюк, который я использую, когда мне нужна функция, не являющаяся шаблоном, заключается в том, чтобы обернуть мой код в лямбду.

      struct Empty
{
};


void foo()
{
    /* foo is not template, will not compile
    if constexpr ( false ) 
    {
        std::cout << Empty{}.bar; // does not exist
    }
    */ 

    // now the code is in a lambda with auto param, so basicly a template method
    [&](const auto& empty)
    {
        if constexpr ( false ) 
        {
            std::cout << empty.bar; 
        }
    }(Empty{});
}

демо: https://wandbox.org/permlink/XEgZ6PXPLyyjXDlO

Тот факт, что я могу использовать[&]синтаксис делает это решение более простым в использовании, чем создание вспомогательного метода, поскольку мне не нужно пересылать все параметры, которые мне нужны, внутри моегоif constexpr

Другие вопросы по тегам