"如果c ++ 17中的constexpr"在非模板化函数中不起作用

NYM*_*NYM 19 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);
}
Run Code Online (Sandbox Code Playgroud)

但是,当我重写上面的代码,如下所示,其中,if constexprmain功能:

#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;
}
Run Code Online (Sandbox Code Playgroud)

我收到编译错误:

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

问题不在主要功能中.这可以是类似于以下的任何功能.

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();
}
Run Code Online (Sandbox Code Playgroud)

我想知道为什么if constexpr只能在模板函数中工作,即使类型是由输入参数的decltype推导出来的.

Bar*_*rry 22

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

这是设计的.

if constexpr如果它在模板中,则不会实例化未被采用的分支.它不仅仅会将分支视为令牌汤,而是避免解析它或完全执行语义分析.双方仍然需要进行分析,因为s的结构不合理,这是一个错误.*valueint

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

  • @hvd将对其执行*some*分析.如果你做`template <typename T> void foo(int i){if constexpr(some_condition_v <T>){std :: cout <<*i; `,仍然可以诊断,因为表达式不依赖(代码格式错误,但不需要诊断). (4认同)
  • 我最初误解了你的答案,所以删除了我之前的评论,但是在模板实例化中,`if constexpr`条件变为常量,未被采用的分支*将被解析,但*不会*对其执行语义分析.这就是重点,这就是OP的'print <int>`工作的原因,即使它包含`*value`,其中`value`是一个`int`.这是语义分析,将其标记为错误. (3认同)
  • @einpoklum不,它没有严格意义上说:图灵完整性是计算抽象(例如编程语言)的属性,而不可判定性是具体计算问题的属性. (3认同)
  • @einpoklum试图取消引用`int`?不.试图取消引用`T`?是. (2认同)

Gui*_*cot 14

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

问题是,它也可以在非模板中工作,只是不是以您期望的方式工作。

为了if constexpr像您所说的那样工作,您不仅需要模板,还需要包含的表达式依赖于模板参数。

让我们一步步解释为什么在 C++ 中要这样做,以及其含义是什么。

让我们从简单开始吧。下面的代码能编译通过吗?

void func_a() {
    nonexistant();
}
Run Code Online (Sandbox Code Playgroud)

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

让我们添加一层。

下面的代码能编译通过吗?

template<typename T>
void func_b_1() {
    nonexistant();
}
Run Code Online (Sandbox Code Playgroud)

如果使用正确的编译器,则不会编译。

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

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

这是有道理的。如果表达式nonexistant()不依赖于T,为什么它的含义会随着 改变Tfunc_a因此,这个表达式在编译器看来是一样的。

那么从属名称又如何呢?

template<typename T>
void func_b_2() {
    T::nonexistant();
}
Run Code Online (Sandbox Code Playgroud)

这将编译!这是为什么?这段代码中没有任何地方有一个名为 的函数nonexistant。然而,您将其作为整个代码库输入编译器,它会很乐意接受它。

标准甚至说它必须接受它。这是因为某处可能存在T包含nonexistant。因此,如果您使用具有静态成员函数的类型实例化模板,nonexistant它将编译并调用该函数。如果您使用不具有该函数的类型实例化模板,则它将无法编译。

正如您所看到的,名称查找是在实例化期间完成的。这称为第二阶段名称查找。第二阶段名称查找仅在实例化期间完成。

现在,输入if constexpr.

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

extern int a;

void helper_1(int*);

void func_c() {
    if constexpr (false) {
        helper_1(&a);
    }
}
Run Code Online (Sandbox Code Playgroud)

答案是helper_1,并且a没有使用ODR。我们可以离开helper_1并且a未定义,并且不会出现链接器错误。

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

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

void func_d() {
    if constexpr (false) {
        helper_2<int>();
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码无法使用普通的if.

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

现在让我们混合一下:

template<typename T>
void func_b_3() {
    if constexpr (false) {
        nonexistant();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

void func_e() {
    if constexpr (false) {
        nonexistant();
    }
}
Run Code Online (Sandbox Code Playgroud)

即使未实例化 的内容if constexpr,也会发生错误,因为第一个名称查找步骤已完成,并且错误发生在实例化过程之前。只是本例中并没有实例化,不过此时也无所谓了。


那么有什么用途呢if constexpr?为什么它似乎只能在模板中工作?

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

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

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

void func_f() {
    helper_3<int>();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


lis*_*rus 8

C++标准,第9.4.1节:

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

(强调我的)

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