"Если 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
плохо сформирован для int
s, это ошибка.
Вы просто не можете использовать 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