"如果是constexpr"在C ++ 17中,在非模板化函数中不起作用

时间:2018-04-26 20:38:21

标签: c++ c++17 if-constexpr

我尝试使用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推导出来的。

4 个答案:

答案 0 :(得分:22)

  

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

这是设计的。

if constexpr实例化未在模板中使用的分支。它不会仅仅将分支视为令牌汤,而是避免解析它或完全执行语义分析。双方仍将被分析,并且由于*value int的格式不正确,这是一个错误。

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

答案 1 :(得分:8)

C ++标准,第9.4.1节:

  

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

(强调我的)

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

答案 2 :(得分:3)

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

here image

答案 3 :(得分:1)

<块引用>

我想知道为什么 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 的用途是什么?为什么它似乎只适用于模板?

问题是,它在模板中的工作方式并没有什么不同。正如我们在 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 的表达式,名称查找在第二阶段完成。名称查找的第二阶段在模板实例化期间完成。包含 if constexprT::nonexistant() 分支总是被丢弃的部分,因此名称查找的第二阶段永远不会完成

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

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