Why not infer template parameter from constructor?

Viewed 20743

my question today is pretty simple: why can't the compiler infer template parameters from class constructors, much as it can do from function parameters? For example, why couldn't the following code be valid:

template <typename obj>
class Variable {
    obj data;
public:
    Variable(obj d) { data = d; }
};

int main() {
    int num = 2;
    Variable var(num); // would be equivalent to Variable<int> var(num),
    return 0;          // but actually a compile error
}

As I say, I understand that this isn't valid, so my question is why isn't it? Would allowing this create any major syntactic holes? Is there an instance where one wouldn't want this functionality (where inferring a type would cause issues)? I'm just trying to understand the logic behind allowing template inference for functions, yet not for suitably-constructed classes.

12 Answers

I think it is not valid because the constructor isn't always the only point of entry of the class (I am talking about copy constructor and operator=). So suppose you are using your class like this :

MyClass m(string s);
MyClass *pm;
*pm = m;

I am not sure if it would be so obvious for the parser to know what template type is the MyClass pm;

Not sure if what I said make sense but feel free to add some comment, that's an interesting question.

C++ 17

It is accepted that C++17 will have type deduction from constructor arguments.

Examples:

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);

Accepted paper.

Still missing: It makes the following code quite ambiguous:

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}

Supposing that the compiler supports what you asked. Then this code is valid:

Variable v1( 10); // Variable<int>

// Some code here

Variable v2( 20.4); // Variable<double>

Now, I have the same type name (Variable) in the code for two different types (Variable and Variable). From my subjective point of view, it affects the readability of the code pretty much. Having same type name for two different types in the same namespace looks misleading to me.

Later update: Another thing to consider: partial (or full) template specialization.

What if I specialize Variable and provide no constructor like you expect?

So I would have:

template<>
class Variable<int>
{
// Provide default constructor only.
};

Then I have the code:

Variable v( 10);

What should the compiler do? Use generic Variable class definition to deduce that it is Variable, then discover that Variable doesn't provide one parameter constructor?

A lot of classes don't depend on constructor parameters. There are only a few classes that have only one constructor, and parameterize based on this constructor's type(s).

If you really need template inference, use a helper function:

template<typename obj>
class Variable 
{
      obj data;
public: 
      Variable(obj d)
      : data(d)
      { }
};

template<typename obj>
inline Variable<obj> makeVariable(const obj& d)
{
    return Variable<obj>(d);
}

Deduction of types is limited to template functions in current C++, but it's long been realised that type deduction in other contexts would be very useful. Hence C++0x's auto.

While exactly what you suggest won't be possible in C++0x, the following shows you can get pretty close:

template <class X>
Variable<typename std::remove_reference<X>::type> MakeVariable(X&& x)
{
    // remove reference required for the case that x is an lvalue
    return Variable<typename std::remove_reference<X>::type>(std::forward(x));
}

void test()
{
    auto v = MakeVariable(2); // v is of type Variable<int>
}

You are right the compiler could easily guess, but it's not in the standard or C++0x as far as I know so you'll have to wait atleast 10 more years (ISO standards fixed turn around rate) before compiller providers add this feature

Let's look at the problem with reference to a class everyone should be familar with - std::vector.

Firstly, a very common use of vector is to use the constructor that takes no parameters:

vector <int> v;

In this case, obviously no inference can be performed.

A second common use is to create a pre-sized vector:

vector <string> v(100);

Here, if inference were used:

vector v(100);

we get a vector of ints, not strings, and presumably it isn't sized!

Lastly, consider constructors that take multiple parameters - with "inference":

vector v( 100, foobar() );      // foobar is some class

Which parameter should be used for inference? We would need some way of telling the compiler that it should be the second one.

With all these problems for a class as simple as vector, it's easy to see why inference is not used.

Making the ctor a template the Variable can have only one form but various ctors:

class Variable {
      obj data; // let the compiler guess
      public:
      template<typename obj>
      Variable(obj d)
       {
           data = d;
       }
};

int main()
{
    int num = 2;
    Variable var(num);  // Variable::data int?

    float num2 = 2.0f;
    Variable var2(num2);  // Variable::data float?
    return 0;         
}

See? We can not have multiple Variable::data members.

Related