未正确推导的全局函数引用

问题描述 投票:0回答:1

我正在使用 gcc12.2 并发现以下代码编译并产生奇怪的结果在 godbolt 中尝试。 (PS:切换到clang也是一样的结果)

#include <iostream>
void global() { /*std::cout << "global()" << std::endl;*/ }

template <typename T> // universal reference
void invoke(T&& func, const std::string& tag) { 
    if constexpr (std::is_rvalue_reference_v<T>) {
        std::cout << "rvalue reference " << tag << std::endl;
    }
    else if constexpr (std::is_lvalue_reference_v<T>) {
        std::cout << "lvalue reference " << tag << std::endl;
    }
    else {
        std::cout << "non-reference " << tag << std::endl;
    }
    func(); 
}

template <typename T>
struct Test {
    Test(T&& x, const std::string& tag) // rvalue reference, not universal reference 
    {
        if constexpr (std::is_rvalue_reference_v<T>) {
            std::cout << "rvalue reference " << tag << std::endl;
        }
        else if constexpr (std::is_lvalue_reference_v<T>) {
            std::cout << "lvalue reference " << tag << std::endl;
        }
        else {
            std::cout << "non-reference " << tag << std::endl;
        }
    }
};

int main() {
    int && x = 3;
    using RRef = void (&&)();
    RRef rref = global;
    using LRef = void (&)();
    LRef lref = global;

    std::cout << "RRef       is rvalue reference " << std::is_rvalue_reference_v<RRef> << std::endl;
    std::cout << "rref       is rvalue reference " << std::is_rvalue_reference_v<decltype(rref)> << std::endl;
    std::cout << "move(rref) is rvalue reference " << std::is_rvalue_reference_v<decltype(std::move(rref))> << std::endl;
    std::cout << "x          is rvalue reference " << std::is_rvalue_reference_v<decltype(x)> << std::endl;
    std::cout << "move(x)    is rvalue reference " << std::is_rvalue_reference_v<decltype(std::move(x))> << std::endl;

    std::cout << "==== invoke ==== " << std::endl;
    invoke(global, "global");
    invoke(rref, "rref");
    invoke(std::move(rref), "rref2");
    invoke(lref, "lref");
    invoke(std::move(lref), "lref2");

    std::cout << "==== Test ==== " << std::endl;
    Test(global, "global");
    Test(rref, "rref");
    Test(std::move(rref), "rref2");
    Test(lref, "lref");
    Test(std::move(lref), "lref2");

    std::cout << "==== Test int ==== " << std::endl;
    // Test(x, "x");  // cannot bind lvalue to rvalue-reference
    Test(std::move(x), "move(x)");
}

gcc 12.2 的输出如下:

RRef       is rvalue reference 1
rref       is rvalue reference 1
move(rref) is rvalue reference 0   // why is this no longer rvalue reference
x          is rvalue reference 1
move(x)    is rvalue reference 1
==== invoke ==== 
lvalue reference global  // why are they all lvalue reference
lvalue reference rref
lvalue reference rref2
lvalue reference lref
lvalue reference lref2
==== Test ==== 
non-reference global  // why they are non-reference
non-reference rref
non-reference rref2
non-reference lref
non-reference lref2
==== Test int ==== 
non-reference move(x)

能否请您解释一下为什么我们得到以下输出:

  1. std::move(rref)
    是左值引用而
    std::move(x)
    是右值引用
  2. 将函数传递给接受通用引用的
    invoke
    时,推导的类型都是左值引用,说明传递给
    invoke
  3. 的是左值引用
  4. 将函数传递给只接受右值引用的
    Test
    时,各种输入都被接受,说明它们都是右值引用。

传递

int
的引用表现正常,而传递函数的引用表现得非常奇怪。

c++11 rvalue-reference template-argument-deduction
1个回答
0
投票
  1. 尽管

    std::move(x)
    是右值引用,但
    std::move(rref)
    是左值,因为它是标准中的特例。来自 C++14 标准草案 [expr.call/p10]:

    如果结果类型是 左值引用类型或对函数类型的右值引用,则函数调用是左值,如果结果类型是对对象类型的右值引用,则为xvalue,否则为纯右值。

    这在这个答案中有详细解释。

  2. 将值传递给

    invoke()
    时,您可以传递它们

    • 使用它们的变量名(左值):
      invoke(global, "global");
      invoke(rref, "rref");
      
    • 或者通过像这里一样的
      std::move(ref-to-function)
      invoke(std::move(rref), "rref2");
      
      由于(1)中提到的特殊情况,这也是一个左值。

    因此,模板参数在所有情况下都绑定到左值。

  3. 请注意,您的

    Test
    构造函数打印有关模板参数类型
    T
    的信息,而不是有关参数类型
    x
    (即
    T&&
    )的信息。这就是为什么您在期望“右值引用”的地方看到“非引用”的原因。如果我们替换代码以打印有关
    decltype(x)
    的信息而不是
    T
    ,我们将得到预期的“右值引用”结果。

在任何情况下,rvalue-reference-to-function 的使用都非常罕见和晦涩,通常左值引用足以满足大多数需求(尽管 here 是一个需要右值引用的例子)。

© www.soinside.com 2019 - 2024. All rights reserved.