14

Consider the following class:

template<typename T1, typename T2>
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
}

The following is not allowed in c++:

auto p = Pair(10, 10);

Why isn't that allowed? the types can be completely determined from the constructor call.
I know there are workaround for that like:

template<typename T1, typename T2>
Pair<T1, T2> MakePair(const T1 &First, const T2 &Second)
{
    return Pair<T1, T2>(First, Second);
}

But why is that needed? Why doesn't the compiler just determine the type from the arguments just like it does from function template? You might say its because the standard doesn't allow it, so why doesn't the standard allow it?

Edit:
For those who say this is an example why this shouldn't be allowed:

template<typename T1, typename T2>
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
         Pair(const T2 &Second, const T1 &First) : First(First), Second(Second) { }
};

auto p = Pair(10,1.0);

I can do exactly this with function templates overloading:

template<typename T1, typename T2>
Pair<T1, T2> MakePair(const T1 &First, const T2 &Second)
{
    return Pair<T1, T2>(First, Second);
}
template<typename T1, typename T2>
Pair<T1, T2> MakePair(const T2 &Second, const T1 &First)
{
    return Pair<T1, T2>(First, Second);
}

Why is this allowed for functions but not for classes?

Daniel
  • 30,896
  • 18
  • 85
  • 139
  • 1
    because constructors don't have names and it's impossible to call them, argument deduction works only for function calls. – Gene Bushuyev Oct 27 '11 at 19:49
  • @GeneBushuyev: You specify that you want a constructor by "calling" the class. – Daniel Oct 27 '11 at 19:52
  • 1
    `I can do exactly this with function templates overloading:` Actually, no you can't, for the same reasons. It's ambiguous. http://codepad.org/axjCAZyL – Mooing Duck Oct 27 '11 at 20:02
  • @Mooing: I think he's saying he can create the same ambiguities with function overloading as exist with constructor overloading, in which case, you need to specify the template arguments. – Benjamin Lindley Oct 27 '11 at 20:05
  • The two `MakePair` implementations at the end are ambiguous, you can write them, but you will have to specify the types for the function template, as the compiler cannot pick up the best overload. – David Rodríguez - dribeas Oct 27 '11 at 20:32

3 Answers3

10

This is what Bjarne Stroustrup has to say on the matter:

Note that class template arguments are never deduced. The reason is that the flexibility provided by several constructors for a class would make such deduction impossible in many cases and obscure in many more.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • Same thing can be said about function template overloading – Daniel Oct 27 '11 at 19:45
  • The difference is there is no ambiguity about the type being created by a function template overload, because no type is being created. The above is not well-worded, but I hope you get my meaning? – John Dibling Oct 27 '11 at 19:50
  • Long time ago when `auto` was first introduced, I suggested using that syntax for ctors argument deduction, e.g. `pair(10,10)`. That can be mixed with using alias-declaration: `using mypair = pair` so one could write `mypair p(10,10);` instead of `auto p = make_pair(10,10);`. That syntax would have made helper functions `make_something()` (which serve no other purpose than type deduction) unnecessary. – Gene Bushuyev Oct 27 '11 at 20:04
5

Consider the following.

template<typename T1, typename T2>
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
         Pair(const T2 &Second, const T1 &First) : First(First), Second(Second) { }
};

auto p = Pair(10,1.0);

Should p be a Pair<int,double> or a Pair<double,int>?

Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • @Dani: I see your point. Why can't it deduce the types in cases where it is unambiguous, right? I'll have to think about that. – Benjamin Lindley Oct 27 '11 at 20:02
  • @Benjamin: Because it's not unambigious. – John Dibling Oct 27 '11 at 20:05
  • @John: If there's no overloads like the one in the class I showed, and no specializations, how is it ambiguous? – Benjamin Lindley Oct 27 '11 at 20:07
  • IOW, what else could `Pair(10,10.0)` mean besides `Pair(10,10.0)`? – Benjamin Lindley Oct 27 '11 at 20:10
  • @Dani: The added example (the two `MakePair`) is ambiguous. While you can write it, you cannot call them without manually specifying the types, and that in turn means that you are back at step one: having to make the types explicit. – David Rodríguez - dribeas Oct 27 '11 at 20:33
  • @BenjaminLindley: in all the code on this page so far, `Pair()` matches two different functions. The _types_ aren't ambiguous, the _functions_ are. – Mooing Duck Oct 27 '11 at 20:40
  • @David: but in case it's not ambiguous, why isn't it allowed? You said its because it might be ambiguous so I provided an example of functions that might be ambiguous too, but it's allowed in functions in the general case but not in classes in the general case. – Daniel Oct 27 '11 at 20:41
  • @Mooing Duck: I don't follow you. In all the code on this page so far, `Pair()` matches nothing, as neither my class nor Dani's has a default constructor. – Benjamin Lindley Oct 27 '11 at 20:44
  • 1
    Er, oops. `Pair(10, 10.0)` matches both `Pair::Pair(const T1 &First, const T2 &Second)` and `Pair::Pair(const T2 &Second, const T1 &First)`. `MakePair(10,10.0)` matches both `MakePair(const T1 &First, const T2 &Second)` and `MakePair(const T2 &Second, const T1 &First)`, so in all cases, the calls are ambiguous. – Mooing Duck Oct 27 '11 at 20:49
  • @Mooing: But I'm referring to the OP's original class, where there is no `Pair::Pair(const T2 &Second, const T1 &First)`, where's the ambiguity there? – Benjamin Lindley Oct 27 '11 at 20:51
  • Oh, in the OP's original class, there's no ambiguity. I can't think of a reason that the language designers forbade it. Bjarne Stroustrup's quote isn't much of a reason really. – Mooing Duck Oct 27 '11 at 21:01
  • @Dani, in general features are dropped because the value they add does not compensate the extra complexity in either the compiler or user programs. Do you know (without using a compiler) what the type of `std::min( 10, 5.0 )` is? – David Rodríguez - dribeas Oct 27 '11 at 21:41
2

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 )?

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 3
    You underestimate the burden of creating helper functions, keeping them in sync, and also namespace pollution. Writing template parser library I found a large number of functions are those helper functions, in fact I have plenty of _function.h files with only helpers. Namespace pollution is also unpleasant result of this, because using `make_something` is usually bad naming, and virtually every class has to be instantiated via helper, the functions took the relevant names, and the types to avoid clashes had to be modified with prefix. – Gene Bushuyev Oct 27 '11 at 22:51
  • Not arguing with the general complexity point, but re the specific issue "see that there is a templated constructor... `pair( U f, V s )`" - the constructors been called with a *single* (`pair`) argument so wouldn't the matching would be unambiguously best with [`std::pair(pair&&)` (8) at cppreference](http://en.cppreference.com/w/cpp/utility/pair/pair)? – Tony Delroy Aug 20 '14 at 05:54