繁体   English   中英

C++17 中的“如果 constexpr”在非模板 function 中不起作用

[英]"If constexpr" in C++17 does not work in a non-templated function

我尝试使用 C++17 标准。 if constexpr ,我尝试使用 C++17 的功能之一。 我有一个问题......请看下面的代码。 这编译没有错误。 在下面的代码中,我尝试使用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 constexprmain function中:

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

问题不在主要 function 上。 这可以是类似于以下的任何 function。

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 推导出来的。

我想知道为什么“ if constexpr ”仅适用于模板函数,即使类型是由输入参数的decltype推导出来的。

这是设计使然。

if constexpr不会实例化未采用的分支,如果它在模板中 它不会只是将没有被视为令牌汤的分支视为完全解析或执行语义分析。 双方仍将被分析,并且由于*value对于int s int错误,这是一个错误。

您根本无法使用if constexpr来避免编译非模板代码。 这只是为了避免实例化可能对特定专业化无效的模板代码。

C++ 标准,条款 9.4.1:

如果 if 语句的形式为 if constexpr,则条件的值应为 bool (8.6) 类型的上下文转换常量表达式; 这种形式称为 constexpr if 语句。 如果转换条件的值为假,则第一个子语句是丢弃的语句,否则第二个子语句(如果存在)是丢弃的语句。 在封闭模板化实体的实例化期间(第 17 条),如果条件在实例化后不依赖于值,则不会实例化丢弃的子语句(如果有)。

(强调我的)

因此, constexpr if的子constexpr if不在模板内,它仍然会被实例化,因此它至少必须编译。

在模板之外,完全检查丢弃的语句。 if constexpr 不能替代 #if 预处理指令。

这里图片

我想知道为什么if constexpr仅适用于模板函数,即使类型是由输入参数的 decltype 推导出来的。

问题是,它也适用于非模板,只是不像您期望的那样。

if constexpr像你说的那样工作,你不仅需要一个模板,而且你需要包含的表达式依赖于模板参数。

让我们一步一步地了解为什么在 C++ 中采用这种方式,以及其含义是什么。

让我们从简单的开始。 下面的代码可以编译吗?

void func_a() {
    nonexistant();
}

我想我们都会同意它不会编译,我们正在尝试使用尚未声明的函数。

让我们添加一层。

下面的代码可以编译吗?

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

使用正确的编译器,这将无法编译。

但这是为什么呢? 您可能会争辩说这段代码从未真正编译过,因为模板从未被实例化。

该标准定义了他们称之为两阶段名称查找的东西。 这是即使模板没有实例化,编译器也必须执行名称查找和任何不依赖于模板参数的内容。

这是有道理的。 如果表达式nonexistant()不依赖于T ,为什么它的含义会随着T改变? 因此,在编译器的眼中,此表达式与func_a中的表达式相同。

现在,输入if constexpr

为了使这种结构与语言的其余部分正常工作,已经决定if constexpr定义为实例化的分支 因此,我们可以使一些代码非实例化,即使在非模板中!

extern int a;

void helper_1(int*);

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

答案是helper_1a没有使用 ODR。 我们可以保留helper_1a未定义,这样就不会出现链接器错误。

更好的是,编译器不会实例化if constexpr的丢弃分支中的模板:

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

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

这段代码不会用正常的if编译。

如您所见, if constexpr的丢弃分支就像一个尚未实例化的模板一样工作,即使在非模板代码中也是如此。

现在让我们混合一下:

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

这就像我们一开始的模板函数一样。 我们说过即使模板没有被实例化,代码也是无效的,因为无效的表达式不依赖于T 我们也说过, if constexpr是实例化过程中的一个分支 错误发生实例化之前 这段代码也不会编译。

所以最后,这段代码也不会编译:

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

即使没有实例化if constexpr的内容, if constexpr发生错误,因为完成了第一个名称查找步骤,并且错误发生在实例化过程之前。 只是在这种情况下,没有实例化,但此时无所谓。


那么if constexpr什么用途呢? 为什么它似乎只适用于模板?

问题是,它在模板中的工作方式并没有什么不同。 正如我们在func_b_2看到的func_b_2 ,错误仍然发生。

但是,这种情况将起作用:

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

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

表达式int::nonexistant()无效,但代码可以编译。 这是因为由于T::nonexistant()是一个依赖于T的表达式,名称查找在第二阶段完成。 名称查找的第二阶段在模板实例化期间完成。 包含T::nonexistant()if constexpr分支总是被丢弃的部分,因此名称查找的第二阶段永远不会完成

你去吧。 if constexpr不是不编译一部分代码。 就像模板一样,它们被编译并完成任何可以进行名称查找的表达式。 if constexpr是关于控制实例化,即使在非模板函数中。 适用于模板的所有规则也适用于if constexpr所有分支。 两阶段名称查找仍然适用,并允许程序员不实例化如果实例化则不会编译的某些代码部分。

因此,如果代码无法在未实例化的模板中编译,则不会在未实例化的if constexpr分支中编译。

我参加聚会有点晚了,但是当我需要非模板 function 中的if constexpr是将我的代码包装在 lambda 中时,我会使用一个不错的技巧。

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

    // 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中转发我需要的所有参数

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM