c++stlstd-functionstdany

Why does std::function::operator= in C++ always construct and not assign object?


This query is mainly based on std::function<R(Args...) >::operator= and std::any::operator=. The documentation shows that they are always implemented by constructing a new temporary object and swapping it with this object using the swap function, which is equivalent to destructing the retained object and calling the construction operation of the retained object (T(T)) to construct a new object. And std::variable<Types... >::operator= calls the assignment of the retained object (operator=(T)) when the LHS and RHS have the same object. The question is, why does std::function<R(Args...) >::operator= and std::any::operator= destruct the original object and construct the new object via a constructor operation, regardless of whether the new object is the same as the retained object? Wouldn't it be better to construct by assignment than by destructuring?

I have checked the documentation and searched many web pages and did not find a detailed explanation, it seems that everyone does this by convention. I would like to know the reason for using swap to handle assignment operations when implementing std::any and std::function with small object optimizations, and its best practice.


Solution

  • I'll skip std::function and focus on std::any.

    The standard specifies that most overloads of its operator= must act as if doing a swap operation. For example,

    any& operator=( const any& rhs );
    

    Assigns by copying the state of rhs, as if by any(rhs).swap(*this)

    see the C++ Reference explanation at https://en.cppreference.com/w/cpp/utility/any/operator%3D.

    In this way the standard suggests to the implementers a possible implementation. The question is now: why's that? The answer seems to lie in the type requirements for the template parameter of std::any:

    The class any describes a type-safe container for single values of any copy constructible type. .

    The property of being copy-constructible is one of the most fundamental an common properties in C++. It does not even require that values of that type be assignable! See: https://en.cppreference.com/w/cpp/concepts/copy_constructible for a detailed explanation of this property. Thus, std::any::operator= must not rely on the value assignment operator to exist, though it may use it, if it is available. It only invokes a member function swap of std::any, which is always defined.

    To sum all this up, C++ tries its standard library to be as general as possible, and this is why it uses swaps for the operations you're asking about.

    Finally, a simple program that uses std::any with a type that is not assignable, and still std::any<X>::operator= compiles and works:

    #include <any>
    
    struct X
    {
      X& operator=(const X&) = delete;
    };
    
    int main()
    {
      X x1;
      X x2;
      std::any a = x1;
      std::any b = x2;
      a = b;
    }
    

    Edit

    Here's my attempt to check if gcc uses operator=, or whether the property of "copy-constructible" is used throughout the implementation:

    #include <any>
    #include <iostream>
    
    struct X
    {
      X& operator=(const X&)
      {
        std::cout << "op =\n";
        return *this;
      }
      X& operator=(const X&&)
      {
        std::cout << "move op =\n";
        return *this;
      }
      X(X&&)
      { 
        std::cout << "(&&)\n";
      };
      X(const X&)
      {
        std::cout << "(&)\n";
      }
      X()
      {
        std::cout << "()\n";
      }
    
    //  int tab[1000];
    };
    
    int main()
    {
      X x1;
      X x2;
      std::any a (x1);
      std::any b = x2;
      std::any c = 0;
      a = b;
      std::cout << sizeof(a) << "\n";
      std::cout << sizeof(c) << "\n";
    }
    

    This yields

    ()
    ()
    (&)
    (&)
    (&)
    16
    16
    

    irrespective of whether I uncomment the // int tab[1000]; line or not (to play with the small-size object optimization). This kinda shows the implementation of std:any needs not operator= of the value type in the implementation of its own operator=.