views:

757

answers:

10

Hi all, 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.

Thanks

A: 

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

Robert Gould
That's not correct with the upcoming standard an auto keyword will be introduced. Take a look at the post of James Hopkins in this thread. http://stackoverflow.com/questions/984394/why-not-infer-template-parameter-from-constructor/986197#986197. He shows how it will be possible in C++0x.
ovanes
Just to correct myself, auto keyword is also present in the current standard, but for the different purpose.
ovanes
A: 

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.

Nick D
That wouldn't make sense under any scenario. obj in terms of obj data is undefined since that class is no longer a template. Such code would be invalid either way.
GRB
Yes you are right, I misread your question :)
Nick D
I wanted the compiler behavior that you describe, so I figure out a way to bypass that restriction (in my case), which you may find interesting, http://stackoverflow.com/questions/228620/garbage-collection-in-c-why/959189#959189
Nick D
+1  A: 

See The C++ Template Argument Deduction for more info on this.

Igor Krivokon
I read this article before and it didn't seem to talk much about what I'm saying. The only time the writer seems to talk about argument deduction with regards to classes is when he says it can't be done at the top of the article ;) -- if you could point out the sections which you think are relevant though I'd really appreciate that.
GRB
+9  A: 

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.

Drahakar
This is actually a great point which I never considered. I don't see any way around the fact that the pointer would have to be type specific (i.e. it would have to be MyClass<string>* pm). If that's the case, then all you would end up doing is saving yourself from specifying the type at instantiation; a few mere characters of extra work (and only if the object is made on the stack, not the heap, as per above). I always suspected that class inference may open a syntactic can of worms, and I think this may be it.
GRB
I should add that this isn't to say class inference couldn't still be done, it's just that there may be few benefits to be gained by allowing it (and may not be worth the work).
GRB
you can add template ctor to accept any type, same applies to assignment operator. What you say does not make sense. C++ standard defines the overload and specialization precedence for function overloads, templates, template specializations etc. Having ctors: MyClass(string const template<class T> MyClass(T const template<> MyClass(int i); when calling will result in: MyClass m1(std::string("aaa")); //calls MyClass(string const
ovanes
@ovanes: Of course, but this would only work with variable that are declared inside the CTor scope, it wouldn't make the compiler determine the class attribute type.
Drahakar
please take a look at my post. I gave an implementation idea. This way you can enforce type erasure and promote variables outside the ctor's scope ;)
ovanes
I've decided to accept this answer, although Pitis and MSalters have posted equally worthy answers. Essentially, the three of you have shown that allowing inference as I've suggested results in syntactic issues that would need to be resolved. That isn't to say those issues couldn't be resolved, it just may be more work than it's worth given that inference could only hope to work in a few relatively limited scenarios (instantiation, maybe references and maybe assignment). While it just seemed *so* simple in my question, when it comes to real life, it's much more difficult ;)
GRB
Since template deduction often fails where the argument type is ambiguous, I see no reason why it couldn't be allowed to fail here (e.g. fail with MyClass * foo), and yet implement the feature where it would *not* be ambiguous. Example: `void func(MyClass<int> val){}` `func(Myclass((int)2));`
Catskul
+1  A: 

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);
}
rlbond
Of course this functionality would only prove useful for some classes, but the same can be said for function inference. Not all templated functions take their parameters from the argument list either, yet we allow inference for those functions that do.
GRB
+2  A: 

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?

Cătălin Pitiș
Worse: what if you only have Variable<int>::Variable(float) ? You now have two ways to deduce Variable(1f) and no way to deduce Variable(1).
MSalters
It's a good point, but could be easely surpassed by casting: Variable v1( (double)10)
jpinto3912
I agree the code readability is a subjective issue, however, I agree 100% with what you're saying on template specialization. The solution would probably be to give an undefined template parameter error (once the compiler looks at the <int> specialization and sees no valid constructors, have it say it has no idea what template you want to use and that you must specify explicitly) but I agree that it's not a pretty solution. I would add this as another major syntactic hole that would need to be dealt with (but could be solved if one accepts the consequences).
GRB
@jpinto3912 - you're missing the point. The compiler has to instantiate ALL possible Variable<T>'s to check if ANY ctor Variable<T>::Variable provides an ambiguous ctor. Getting rid of the ambiguity isn't the problem - simple instantiate Variable<double> yourself if that's what you want. It's finding that ambiguity in the first place which makes it impossible.
MSalters
+3  A: 

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.

anon
I think you're misunderstanding the idea. Type inference for constructors would only occur IF the template type is part of the constructor. Assume that vector has the template definition template<typename T>. Your example isn't a problem because vector's constructor would be defined as vector(int size), not vector(T size). Only in the case of vector(T size) would any inference occur; in the first example, the compiler would give an error saying that T is undefined. Essentially identical to how function template inference works.
GRB
So it would only take place for constructors that have a single parameter and where that parameter is a template parameter type? That seems a vanishingly small number of instances.
anon
It need not necessarily be a single parameter. For example, one could have a vector constructor of vector(int size, T firstElement). If a template has multiple parameters (template<typename T, typename U>), one could have Holder::Holder(T firstObject, U secondObject). If a template has multiple parameters but the constructor only takes one of them, e.g. Holder(U secondObject), then T would always have to be explicitly stated. The rules would be intended to be as similar to function template inference as possible.
GRB
+2  A: 

What you are trying to achieve is called type erasure. Take a look at boost::any and it's implementation how it works.

Boost Any

Now to the question, why it does not work. As boost::any demonstrates it is possible to implement. And it works, but your problem would be the dispatching which type is really inside. Contrary to template approach, you will be required to do dispatching at application runtime, since the contained type will be erased. There are 2 possibilities to handle that: Visitor pattern and custom cast implementation (which throws an exception if you are trying to cast to wrong type). In both cases you move compile time type safety to runtime and bypass the compiler's type checks.

Another approach is to introduce the Variant type: Boost Variant

Variant works differently as boost::any and allows only limited number of types to be stored. This introduces higher type safety, since you really limit the set of expected types. There is a nice article written by Andrey Alexandrescu in ddj, on how to implement such a variant: Part 1, Part 2, Part 3

It's implementation is a bit more complex as that of boost::any, but offer higher type safety and disallows users to put any possible types into the variant, with exception of those being explicitly declared.

As I said it can be implemented in C++, but requires deep knowledge of the language and good interface design, so that users of that class do not handle apples as peaches.

Please remember Henry Spencer's words: If you lie to the compiler, it will get its revenge.

Regards,

Ovanes


Idea of implementation (!untested!)

class any_type
{
    class any_container
    {
    public:
     virtual ~any_container(){}
     virtual void* pointer()=0;
     virtual type_info const& get_type()const=0;
    };

    template<class T>
    class any_container_impl
    {
     T t_;

    public:
     any_container_impl(T const& t)
      : t_(t)
     {}

     virtual ~any_container_impl(){}

     virtual void* pointer()
     {
      return &t_;
     }

     virtual type_info const& get_type()const
     {
      return typeid(T);
     }

    };

    std::auto_ptr<any_container> content_;

public:
    template<class T>
    any_type(T const& t)
     : content_(new any_container_impl<T>(t))
    {}

    template<class T>
    T* cast_to_ptr()
    {
     if(typeid(T)!=content_->get_type())
      return NULL;
     return reinterpret_cast<T*>(content_->pointer());
    }

    template<class T>
    T& cast_to_ref()
    {
     T* ptr = cast_to_ptr<T>();
     if(!ptr)
      throw std::logic_error("wrong type");
     return *ptr;
    }
};


ovanes
Id really like to see this implementation, sounds like a great exercise. By the way, nice quote :D
Drahakar
+1  A: 

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>
}
James Hopkin
+1  A: 

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>> ?
}
MSalters
Another good point. Assuming that there exists a copy constructor defined Variable(Variable<obj> d), there would have to be some sort of precedence established.
GRB
Or, alternatively, have the compiler throw an undefined template parameter error again, much like I suggested with regards to Pitis's answer. However, if you take that route, the number of times where inference can happen without problems (errors) is getting smaller and smaller.
GRB