vendredi 14 avril 2023

reference of global functions not deduced correctly

I am using gcc12.2 and find that the following code compiles and produces wierd results try it in godbolt. (PS: Switching to clang shows the same result)

#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)");
}

The output is as following for 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)

Could you please explain why we get the following output:

  1. std::move(rref) is an lvalue reference while std::move(x) is an rvalue reference
  2. when passing the functions to invoke, which accepts universal reference, the deduced type are all lvalue-reference, indicating lvalue references are passed to the invoke
  3. when passing the functions to Test, which accepts only rvalue references, the various inputs are all accepted, indicating they are all rvalue references.

Passing reference of int behaves normal, while passing reference of function behaves quite wierd.

Aucun commentaire:

Enregistrer un commentaire