c++ - 未正确推断对全局函数的引用

标签 c++ c++11 rvalue-reference template-argument-deduction

我正在使用 gcc 12.2 并发现以下代码编译并产生奇怪的结果 try it in Godbolt 。 (P.S.切换到 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. 将函数传递给仅接受右值引用的 Test 时,各种输入都被接受,表明它们都是右值引用。

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

最佳答案

  1. 尽管 std::move(x) 是右值引用,但 std::move(rref) 也是左值,因为它是一种特殊情况在标准中。来自 C++14 标准 [expr.call/p10] :

    A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.

    这在this中有详细解释。回答。

  2. 将值传递给 invoke() 时,您可以传递它们

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

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

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

    至于为什么 Test 构造函数中的右值引用接受左值输入。这又是标准中的一个特殊情况,允许将右值引用绑定(bind)到函数左值。来自 C++14 标准 [over.ics.ref/3] :

    Except for an implicit object parameter, for which see [over.match.funcs], a standard conversion sequence cannot be formed if it requires binding an lvalue reference other than a reference to a non-volatile const type to an rvalue or binding an rvalue reference to an lvalue other than a function lvalue.

无论如何,对函数的右值引用的使用是相当罕见和晦涩的,通常左值引用足以满足大多数需求(尽管 here 是需要右值引用时的一个例子)。

关于c++ - 未正确推断对全局函数的引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76020666/

相关文章:

c++ - 带有可移动、不可复制参数的 std::thread

c++ - 为什么 `operator<<` 的 `basic_ostream` 的右值重载会返回左值引用?

c++ - 问一个关于 eigen library with raw buffer 的问题

c++ - 选择调用似乎没有超时

c++ - 为什么要这样进行乘法?

c++ - 如何在 wxWidgets 中创建与 Windows (OS) 控制按钮完全一样的控制按钮?

c++ - decltype 中的可变参数模板包

c++ - 绑定(bind)模板化标准库函数

c++ - 在基于范围内自动传递的参数

c++ - 模板化函数只接受右值