Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do C++ sequence containers have an "assign" method but associative containers do not? [closed]

The "four basic kinds" of C++ sequence containers (vector, forward_list, list, and deque) all have an assign method. For example, std::vector has (quoting cppreference.com):

void assign( size_type count, const T& value );

template< class InputIt >
void assign( InputIt first, InputIt last );

void assign( std::initializer_list<T> ilist );

These all have semantics that are, as far as I can tell, the same as using operator= on a temporary constructed using the arguments to the assign method. Why have a separate method to do, for example:

vec.assign({1,2,3});

instead of just:

vec = {1,2,3};

?

Meanwhile, the C++ associative containers such as std::map do not have an assign method. Why not? What reason is sufficiently compelling for the sequence containers to have it but not associative containers? In particular, my initial guess for why sequence containers have assign is it avoids creating a temporary in the applicable circumstance, but the same reasoning would apply to the associative containers, right? (And with move assignment, creating the temporary is probably insignificant anyway.)

This question has a practical application: I am creating a container that has characteristics of both a sequence and associative container, and wondering if there is a good reason to supply an assign method for it.


Some have claimed that this question is opinion-based, and hence inappropriate to ask on this site, but I think that is incorrect. I am not asking what the reader would do when designing a language, I am asking for the rationale used by the C++ language designers when designing the C++ language. This has an objectively correct answer: this decision was made by specific people for specific reasons. The question seeks those reasons.

There are several fact-based ways to answer a question like this:

  • There could be a clear, convincing technical basis for the decision, such that almost any competent designer would make the same choice. In this case, one needs only show a significant (say) performance advantage to having assign for sequence containers and a corresponding lack thereof for associative containers. These performance characteristics are not opinions. The highly-upvoted answer by JaMiT took this approach. (Whether a claimed technical basis is "convincing" is itself subjective, but that is a property of the answer, not the question, and even if one technical argument is found lacking, that does not exclude the possibility of a stronger one that hasn't yet been articulated.)

  • There could be a contemporaneous rationale document. I wasn't able to find one addressing this specific question, but that does not mean it does not exist. Any such document would definitively answer the question, reinforcing the claim that this question has an objectively correct answer, even if it may be challenging to discover it.

  • Similarly, one of the people involved in or witness to the decision could provide an answer based on that personal experience. In that case, one could potentially question the credibility of an answer, but that doesn't make it an opinion.

  • As is often the case with language features, there could be a pre-standardization proposal, and associated revision history of that proposal, from which a likely motivation can be at least inferred. Informed speculation based on such a proposal is not the same as an opinion, although speculation does involve an element of subjective judgment. In my answer, I took this approach, treating STL as the pre-standardization proposal.

like image 444
Scott McPeak Avatar asked Dec 06 '25 13:12

Scott McPeak


1 Answers

In particular, my initial guess for why sequence containers have assign is it avoids creating a temporary in the applicable circumstance, but the same reasoning would apply to the associative containers, right? (And with move assignment, creating the temporary is probably insignificant anyway.)

I think you've hit on two key points here, but you're missing some context and history.
(Nitpick: it's the cost of move assignment that is insignificant, not the creation of the temporary.)

An assign method does avoid creating a temporary, but what is the cost of copying the temporary to the desired object? The cost is linear in the size of the container. Compare this to the cost of constructing the temporary object. For the sequence containers, the cost of construction is also linear in the size. However, for associative containers, the cost of construction has a logarithmic factor (N log(N)), with a possible (up to the implementation) exception in the special case where the input is already sorted.

So for sequence containers, the assign method can cut the cost roughly in half, which is a good win. However, for associative containers, the savings of an assign method is insignificant in terms of asymptotic behavior; there is little benefit to justify the complexity of another member function.


At this point, you might be trying to point out that moving is cheaper than copying, which brings us to your second point. Yes, moving a temporary container into a variable is an insignificant cost. However, move assignment was not introduced until C++11, while std::vector::assign, std::deque::assign, and std::list::assign predate C++11. So when the assign method was introduced, it was a savings. In C++11, the benefit was reduced to be insignificant.

You might note that std::array, which was introduced in C++11 (the same revision with move semantics), does not have an assign method. I do not know if that was intentional, but it is significant that no one has successfully pushed for an assign method to be added. There is no need for an assign method when move semantics accomplishes almost the same thing.

I must acknowledge that in contrast, std::forward_list was also introduced in C++11 and does have an assign method. I would attribute that to std::list having the method, and there was a desire to make the interface similar. It's more "that's how it's always been done" than addressing a need.


I am creating a container that has characteristics of both a sequence and associative container, and wondering if there is a good reason to supply an assign method for it.

Skip it. There's little benefit to it since C++11.

like image 66
JaMiT Avatar answered Dec 09 '25 02:12

JaMiT