25

我尝试使用 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中的where是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

4 回答 4

28

我想知道为什么“ if constexpr”只在模板函数中有效,即使类型是由decltype输入参数推导出来的。

这是设计使然。

if constexpr如果它在模板中,则不会实例化未采用的分支。它不会仅仅将没有被视为令牌汤的分支处理并避免对其进行解析或完全执行语义分析。双方仍将被分析,由于s 的格式不正确,因此这是一个错误。*valueint

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

于 2018-04-26T21:00:42.313 回答
12

C++ 标准,第 9.4.1 条:

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

(强调我的)

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

于 2018-04-26T21:02:10.693 回答
4

I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.

The thing is, it also work in non template, just not in the way you would expect.

For if constexpr to work like you stated, you not only need a template, but you need the contained expressions to be dependent on the template parameters.

Let's go step by step why this is made that way in C++, and what are the implications.

Let's start simple. Does the following code compile?

void func_a() {
    nonexistant();
}

I think we will all agree that it won't compile, we are trying to use a function that hasn't been declared.

Let's add one layer.

Does the following code compile?

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

With a correct compiler, this will not compile.

But why is that? You could argue that this code is never actually compiled, since the template is never instantiated.

The standard define something they call two phase name lookup. This is that even if the template is not instantiated, the compiler must perform name lookup an anything that don't depend on the template parameter.

And that make sense. If the expression nonexistant() don't depend on T, why would its meaning change with T? Hence, this expression is the same as in func_a in the eye of the compiler.

So how about dependent names?

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

This will compile! Why is that? Nowhere in this code there's a function called nonexistant. Yet, you feed that into a compiler as the whole codebase and it will gladly accept it.

And the standard even says that it has to accept it. This is since there could be a T containing nonexistant somewhere. So if you instantiate the template with a type that has the static member function nonexistant it will compile and call the function. If you instantiate the template with a type that don't have the function, it won't compile.

As you can see, the name lookup is done during instantiation. This is called second phase name lookup. The second phase name lookup is done only during instantiation.

Now, enter if constexpr.

To make such construct working well with the rest of the language properly, it has been decided that if constexpr is defined as a branch for instantiation. As such, we can make some code non-instantiated, even in non templates!

extern int a;

void helper_1(int*);

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

The answer is that helper_1 and a are not ODR used. We could leave helper_1 and a not defined and there would not be linker errors.

Even better, the compiler won't instantiate templates that are in a discarded branch of a if constexpr:

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

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

This code won't compile with a normal if.

As you can see, the discarded branch of a if constexpr work just like a template that hasn't been instantiated, even in non template code.

Now let's mix it up:

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

This is just like our template function in the beginning. We said that even if the template was not instantiated, the code was invalid, since the invalid expression don't depend on T. We also said that if constexpr is a branch in the instantiation process. The error happen before instantiation. This code won't compile either.

So finally, this code won't compile either:

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

Even though the content of the if constexpr is not instantiated, the error happen because the fist name lookup step is done, and the error happen before the instantiation process. It is just that in this case, there is no instantiation, but it doesn't matter at this point.


So what are the uses of if constexpr? Why does it seem to work only in templates?

The thing is, it doesn't work differently in templates. Just as we saw with func_b_3, the error still happen.

But, this case will work:

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

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

The expression int::nonexistant() is invalid, but the code compile. This is because since T::nonexistant() is an expression that depends on T, name lookup is done in the second phase. The second phase of name lookup is done during template instantiation. The if constexpr branch that contain T::nonexistant() is always the discarded part so the second phase of name lookup is never done.

There you go. if constexpr is not about not compiling a portion of code. Just like template, they are compiled and any expression that name lookup can be done is done. if constexpr is about controlling instantiation, even in non template function. All rules that applies to templates also applies to all branch of the if constexpr. Two phase name lookup still applies and allow programmers to not instantiate some part of the code that would otherwise not compile if instantiated.

So if a code cannot compiled in a template that is not instantiated, it won't compile in the branch of the if constexpr that is not instantiated.

于 2021-01-13T16:30:39.883 回答
3

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

这里 图片

于 2020-06-12T21:05:59.130 回答