samedi 12 juin 2021

What does `decay_copy` in the constructor in a `std::thread` object do?

I am trying to understand the constructor of a std::thread but fail to understand how parameter types are represented/handled. Judging from cppreference, a simplified constructor could be sketched as follows:

class thread {
   public:
    template <class Function, class Arg>
    thread(Function&& f, Arg&& arg) {
        // something happening
        std::invoke(decay_copy(std::forward<Function>(f)),
                    decay_copy(std::forward<Arg>(arg)));
        // something else happening
    }
};

cppreference defines decay_copy as:

template <class T>
std::decay_t<T> decay_copy(T&& v) { return std::forward<T>(v); }

I experimented a bit with the following examples:

struct X{};
int main() {
    X x1{};
    X& x2 = x1;
    auto f = []() { return; };
    thread t1{f, x1}; // arg should be of type X& after the inner std::forward<Arg>(arg);
    thread t2{f, x2}; // arg should be of type X& after the inner std::forward<Arg>(arg);
    thread t3{f, X{}}; // arg should be of type X&& after the inner std::forward<Arg>(arg);
}

According to my analysis, both x1 and x2 are of type lvalue reference after the inner std::forward whileX{} is of type rvalue reference. I believe we somehow need to separate x1 and x2 to pass it by value or by reference. The analysis leads me to three questions:

  • Is the analysis above correct?
  • How does decay_copy untangle the types correctly?
  • After starting at this for some time, I wonder: Oh bother, why is this so involved? Could it be done easier? The answer is, of course, no, but I still lack intuition for the entire operation.

I am grateful for any hints, suggestions, or explanations!

Aucun commentaire:

Enregistrer un commentaire