More is not generally better. When you have to design a system, you have to be careful with how the different features interact, and should opt for the cleanest simplest solution that provides the most to the users. The typical questions start with, what will the feature offer the users, what cost will the feature require and what problems might the feature bring.
In this case, providing type deduction helps in that you can skip providing the type when declaring a variable or when calling a constructor directly:
pair p = pair( 10, 20 );
The declaration of the variable can be done with auto
, so it is no longer a problem, but it could have been the motivating reason for this feature. In the case of the call to the constructor the added value is quite limited, you can provide a named constructor, as there is in the standard library:
auto p = make_pair( 10, 20 );
So at the end of the day, the added value on being able to call the constructor directly is actually limited, as you can always provide a named function that will create the object for you. Note that the potential extra copies are optimized away, which means that there is no extra cost in calling make_pair
vs. directly calling the constructor `pair(10,20)
Now, focus on the negative implications that the feature can have in user code. The feature would be usable in only some cases, where there are no competing constructor overloads, in the same way that it finds the best overload for a function template, but more importantly specializations. The compiler would have to lookup all potential specializations of the class template, and determine the set of constructors that each one of them offers, and then it would have to resolve the best constructor overload and the enclosing class template.
Looking back to the particular example that you provided, there are no specializations for std::pair
, so we are in the simple case, and yet the example code above would fail:
pair p = pair( 10, 20 );
Why? Well, this is actually the problem, this feature would bring quite a bit of confusion to users. As I mentioned, the right-hand-side of the expression can be easily resolved with plain overload resolution (assuming that there are no specializations), and it would deduce the arguments to be int
, both of them. So the first step would deduce the expression to:
pair p = pair<int,int>(10,20);
Now, that is equivalent to pair p( pair<int,int>( 10, 20 ) )
, and we need the second step of resolution, and at this point, the compiler will see that there is a templated constructor:
template <typename first_type, typename second_type>
struct pair {
first_type first;
second_type second;
template <typename U, typename V>
pair( U f, V s ) : first(f), second(s) {}
};
That means that every potential instantiation of std::pair<T,U>
has a perfect match for that constructor call, and the compiler cannot select, nor provide any sensible error message other than ambiguous in too many ways.
Now imagine coming to your favorite Q&A forum on the internet and guess how many questions would arise from this type of details. Ignoring the cost of implementing the feature, the result of having it in the language is that the language would be more complex and harder to write, it would impose an even steeper learning curve, and all that for basically no real value: in the cases where deduction can be applied, it is trivial to write a helper function that will provide the same advantages, like make_pair
with no added cost.
The fact that you must provide the types takes away complexity from the user: there is no need to think on whether the compiler will be able to deduce the correct type for me. Not having to focus on these details leaves you more time to think on the actual problem that you want to solve.
Sometimes less is better.
BTW, auto
resolves this in the most trivial way: it does not need to find a constructor that will match and guess the type from it, but just use the type of the right-hand-side expression, which is much simpler: only works with a single object, and uses that type for the new object. And even then, with the much simplified use case, it was discussed back and forth until a solution was agreed upon, with the details being whether deduction for auto
could be used to create references, or whether const
or volatile
would be part of the deduced type...
Just a thought: without using a compiler, what is the type of the expression std::min( 10, 5.0 )
?