116

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.

Arty
  • 14,883
  • 6
  • 36
  • 69
GRB
  • 1,515
  • 2
  • 11
  • 10
  • I'd invite someone (I an do it, just not right now), to compile Drahakar and Pitis answer (at least) as good counter-examples why it can't work – jpinto3912 Jun 12 '09 at 10:36
  • 2
    Also note that this is easily worked around via `template Variable make_Variable(T&& p) {return Variable(std::forward(p));}` – Mooing Duck Apr 11 '13 at 00:40
  • 3
    You can sort of get what you want var = Variable(n); – QuentinUK Feb 19 '16 at 13:28
  • 21
    **C++17 will allow this!** This proposal was accepted: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html – underscore_d Jun 29 '16 at 14:57
  • 1
    @underscore_d Excellent! About time! It felt natural to me that is the way it should work, and the source of irritation that it didn't. – amdn Jul 19 '16 at 16:34

12 Answers12

51

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.

Incomputable
  • 2,188
  • 1
  • 20
  • 40
Drahakar
  • 5,986
  • 6
  • 43
  • 58
  • 9
    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* 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 Jun 12 '09 at 03:17
  • 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 Jun 12 '09 at 03:22
  • 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& s); template MyClass(T const& t); template<> MyClass(int i); when calling will result in: MyClass m1(std::string("aaa")); //calls MyClass(string const&) MyClass m2(10); //calls MyClass(int) MyClass m3(any_inst); // calls template MyClass(T const&) – ovanes Jun 12 '09 at 15:25
  • @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 Jun 12 '09 at 16:35
  • 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 Jun 12 '09 at 19:09
  • 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 Jun 12 '09 at 19:20
  • 3
    I don't quite see how allowing template-parameter inference from constructors would require allowing non-specialized declarations *without* constructor calls, as in your second line. I.e., `MyClass *pm` here would be invalid for the same reason that a function declared `template void foo();` can't be called without explicit specialization. – Kyle Strand Apr 24 '15 at 23:07
  • 3
    @KyleStrand Yes, by saying 'class template arguments cannot be deduced from their constructors because _[example that does not use any constructor]_', this answer is completely irrelevant. I genuinely can't believe it was accepted, reached +29, took 6 years for someone to notice the glaring problem, and sat without a single downvote for 7 years. Does no one else think while they read, or...? – underscore_d Aug 21 '16 at 19:19
  • 1
    @underscore_d I like how, as it currently stands, this answer says "there might be some problems with this proposal; I'm not sure if what I just said makes sense (!), feel free to comment (!!); and oh by the way this is pretty much exactly how C++17 will work." – Kyle Strand Aug 21 '16 at 21:19
  • 1
    @KyleStrand Ah yes, that's yet another issue, which I noticed but forgot to mention among all the other fun. The edit about C++17 wasn't by the OP... and IMO shouldn't have been approved, but posted as a new answer: it would've been declinable as 'changes meaning of post' even if the post had been non-meaningless to start... I wasn't aware editing-in entirely new sections was fair game and certainly have had less drastic edits rejected, but I guess that's the luck of the draw in terms of which reviewers you get. – underscore_d Aug 21 '16 at 21:45
  • @underscore_d Hmm, this answer does (implicitly) bring up an interesting point, though--prior to the introduction of `auto`, this proposal wouldn't be viable because it conflates the template-type with the object-type. I originally wasn't going to do so, but think I'll work on a new answer that should be an improvement. – Kyle Strand Aug 22 '16 at 17:11
  • 1
    @underscore_d I've gone ahead and added an answer that's much more in-depth. – Kyle Strand Aug 22 '16 at 19:28
  • 1
    @KyleStrand Excellent, hope it rises to the top as it deserves. :-) I use `auto` so much that (despite the original historical context of this thread) I hadn't even considered deduction without it... though nor had I presumed there were any real syntactic barriers to deducing without `auto` if one wished. Anyway, the key thing is that we now have a correct and up-to-date answer, so thanks for putting in the work! – underscore_d Aug 22 '16 at 19:55
  • @underscore_d "I use auto so much that I hadn't even considered deduction without it" -- same here! I think I was explaining how silly this answer was to a non-programmer when I realized the issue. – Kyle Strand Aug 22 '16 at 19:57
  • We need add a C++17 reference link. – Ciro Santilli OurBigBook.com Jan 07 '17 at 12:28
  • OP, could you please just delete the entire original answer, up to Incomputable's addition, which conflicts with your original answer? – Kyle Strand Sep 26 '17 at 18:04
  • 1
    @CiroSantilli刘晓波死六四事件法轮功 I'm not sure what you mean by "reference link" -- do you mean a link to the relevant part of the standard? The C++ standard is not freely available online. – Kyle Strand Sep 26 '17 at 18:05
  • @KyleStrand true, I don't know either. – Ciro Santilli OurBigBook.com Sep 26 '17 at 19:11
  • @CiroSantilli刘晓波死六四事件法轮功 In that case, I think what's here is sufficient. If you'd like, I can also add a link to CppReference (the website) to my answer (below), which also has more details and a link to a working example. – Kyle Strand Sep 26 '17 at 19:19
27

You can't do what you ask for reasons other people have addressed, but you can do this:

template<typename T>
class Variable {
    public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
  return Variable<T>(instance);
}

which for all intent and purposes is the same thing you ask for. If you love encapsulation you can make make_variable a static member function. That's what people call named constructor. So not only does it do what you want, but it's almost called what you want: the compiler is infering the template parameter from the (named) constructor.

NB: any reasonable compiler will optimize away the temporary object when you write something like

auto v = make_variable(instance);
Nathan Phillips
  • 11,899
  • 1
  • 31
  • 24
Lionel
  • 279
  • 3
  • 3
  • 7
    Want to point out that it isn't particularly useful to make function static member in such case because for that you would have to specify template argument for a class to call it anyway so there would be no point in deducing it. – Predelnik Dec 03 '14 at 15:53
  • 3
    And even better in C++11 you can do `auto v = make_variable(instance)` so you don't actually have to specify the type – Claudiu Apr 22 '15 at 06:13
  • 2
    Yeah, lol at the idea of declaring the make function as a `static` member... think about that for juuust a second. That aside: free make functions were indeed _the_ solution, but it's a lot of redundant boilerplate, that while you're typing it, you just _know_ you shouldn't have to because the compiler has access to all the information you're repeating... and thankfully C++17 canonises that. – underscore_d Aug 21 '16 at 19:21
26

In the enlightened age of 2016, with two new standards under our belt since this question was asked and a new one just around the corner, the crucial thing to know is that compilers supporting the C++17 standard will compile your code as-is.

Template-argument deduction for class templates in C++17

Here (courtesy of an edit by Olzhas Zhumabek of the accepted answer) is the paper detailing the relevant changes to the standard.

Addressing concerns from other answers

The current top-rated answer

This answer points out that "copy constructor and operator=" wouldn't know the correct template specializations.

This is nonsense, because the standard copy-constructor and operator= only exist for a known template type:

template <typename T>
class MyClass {
    MyClass(const MyClass&) =default;
    ... etc...
};

// usage example modified from the answer
MyClass m(string("blah blah blah"));
MyClass *pm;   // WHAT IS THIS?
*pm = m;

Here, as I noted in the comments, there is no reason for MyClass *pm to be a legal declaration with or without the new form of inference: MyClass is not a type (it's a template), so it doesn't make sense to declare a pointer of type MyClass. Here's one possible way to fix the example:

MyClass m(string("blah blah blah"));
decltype(m) *pm;               // uses type inference!
*pm = m;

Here, pm is already of the correct type, and so the inference is trivial. Moreover, it's impossible to accidentally mix types when calling the copy-constructor:

MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));

Here, pm will be a pointer to a copy of m. Here, MyClass is being copy-constructed from m—which is of type MyClass<string> (and not of the nonexistent type MyClass). Thus, at the point where pm's type is inferred, there is sufficient information to know that the template-type of m, and therefore the template-type of pm, is string.

Moreover, the following will always raise a compile error:

MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;

This is because the declaration of the copy constructor is not templated:

MyClass(const MyClass&);

Here, the copy-constructor argument's template-type matches the template-type of the class overall; i.e., when MyClass<string> is instantiated, MyClass<string>::MyClass(const MyClass<string>&); is instantiated with it, and when MyClass<int> is instantiated, MyClass<int>::MyClass(const MyClass<int>&); is instantiated. Unless it is explicitly specified or a templatized constructor is declared, there is no reason for the compiler to instantiate MyClass<int>::MyClass(const MyClass<string>&);, which would obviously be inappropriate.

The answer by Cătălin Pitiș

Pitiș gives an example deducing Variable<int> and Variable<double>, then states:

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.

As noted in the previous example, Variable itself is not a type name, even though the new feature makes it look like one syntactically.

Pitiș then asks what would happen if no constructor is given that would permit the appropriate inference. The answer is that no inference is permitted, because the inference is triggered by the constructor call. Without a constructor-call, there is no inference.

This is similar to asking what version of foo is deduced here:

template <typename T> foo();
foo();

The answer is that this code is illegal, for the reason stated.

MSalter's answer

This is, as far as I can tell, the only answer to bring up a legitimate concern about the proposed feature.

The example is:

Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?

The key question is, does the compiler select the type-inferred constructor here or the copy constructor?

Trying the code out, we can see that the copy constructor is selected. To expand on the example:

int num = 3;
Variable var(num);            // infering ctor
Variable var2(var);           // copy ctor
Variable var3(move(var));     // move ctor
Variable var4{Variable(num)}; // infering ctor
// Variable var4(Variable(num));     // illegal

I am not sure how the proposal and the new version of the standard specify this; it appears to be determined by "deduction guides," which are a new bit of standardese that I don't yet understand.

The var4 deduction is illegal due to the "most vexing parse" (the statement is parsed as a function declaration). I'm not totally sure why, because that doesn't look like a valid syntax for a function declaration to me.

Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
  • What a great, detailed answer! `var4` is just a case of the "most vexing parse" (not related to template arg deduction). We used to just use extra parens for this, but these days I think using braces to unambiguously denote construction is the usual advice. – Sumudu Fernando Feb 26 '18 at 07:50
  • @SumuduFernando Thanks! Do you mean that `Variable var4(Variable(num));` is treated as a function declaration? If so, why is `Variable(num)` a valid parameter specification? – Kyle Strand Feb 26 '18 at 20:26
  • @SumuduFernando Never mind, I had no idea this was valid: http://coliru.stacked-crooked.com/a/98c36b8082660941 – Kyle Strand Feb 26 '18 at 22:11
12

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
  • 173,980
  • 10
  • 155
  • 350
  • Another good point. Assuming that there exists a copy constructor defined Variable(Variable d), there would have to be some sort of precedence established. – GRB Jun 12 '09 at 15:10
  • 1
    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 Jun 12 '09 at 15:21
  • This is actually an interesting point, and (as I've noted in my answer) I'm not yet sure how the accepted C++17 proposal resolves this. – Kyle Strand Aug 22 '16 at 20:33
9

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ș
  • 14,123
  • 2
  • 39
  • 62
  • 1
    Worse: what if you only have Variable::Variable(float) ? You now have two ways to deduce Variable(1f) and no way to deduce Variable(1). – MSalters Jun 12 '09 at 09:06
  • It's a good point, but could be easely surpassed by casting: Variable v1( (double)10) – jpinto3912 Jun 12 '09 at 10:31
  • 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 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 Jun 12 '09 at 14:39
  • 4
    @jpinto3912 - you're missing the point. The compiler has to instantiate ALL possible Variable's to check if ANY ctor Variable::Variable provides an ambiguous ctor. Getting rid of the ambiguity isn't the problem - simple instantiate Variable yourself if that's what you want. It's finding that ambiguity in the first place which makes it impossible. – MSalters Jun 12 '09 at 14:56
6

The C++03 and the C++11 standard does not allow for template argument deduction from the parameters passed to the constuructor.

But there is a proposal for "Template parameter deduction for constructors" so you may get what you are asking for soon. Edit: indeed, this feature has been confirmed for C++17.

See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html

ChetS
  • 658
  • 7
  • 15
  • The feature has been added to C++17, but not if "soon" applies to a 6 to 8 year time frame. ;) – ChetS Aug 22 '16 at 23:43
2

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
  • 65,341
  • 56
  • 178
  • 228
  • 1
    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 Jun 12 '09 at 16:51
1

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
  • 13,797
  • 1
  • 42
  • 71
0

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
  • 68,773
  • 61
  • 187
  • 272
  • 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 Jun 12 '09 at 12:30
  • 1
    Just to correct myself, auto keyword is also present in the current standard, but for the different purpose. – ovanes Jun 12 '09 at 12:42
  • Looks like it'll be 8 years (from the time of this answer)...so 10 years was not a bad guess, even though there have been two standards in the mean time! – Kyle Strand Aug 21 '16 at 21:24
-1

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.

  • 3
    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. 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 Jun 12 '09 at 14:23
  • 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. –  Jun 12 '09 at 15:19
  • 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), 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 Jun 12 '09 at 15:38
-2

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 Dandoulakis
  • 42,588
  • 16
  • 104
  • 136
  • 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 Jun 12 '09 at 00:16
  • 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 Dandoulakis Jun 12 '09 at 00:35
-2

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

Igor Krivokon
  • 10,145
  • 1
  • 37
  • 41
  • 4
    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 Jun 12 '09 at 00:27