2

I have a nested template inside a parent template. I'd like instances of the nested template to be convertible to other classes instantiated from the same template (but with different parameters.) To do this, I create a constructor for the nested template that can take different template parameters:

template <class T>
struct Foo
{

    template <class U>
    struct Bar
    {
        Bar() { }

        template <class X, class Y>
        Bar(typename Foo<X>::template Bar<Y> b)
        {

        }
    };

};

This should enable the following expression to compile:

Foo<int>::Bar<int> b = Foo<char>::Bar<char>();



However, it doesn't actually compile. It gives the compiler error:

error: conversion from ‘Foo<char>::Bar<char>’ to non-scalar type ‘Foo<int>::Bar<int>’ requested
  Foo<int>::Bar<int> b = Foo<char>::Bar<char>();

So, I'm confused. Why doesn't this compile?

Siler
  • 8,976
  • 11
  • 64
  • 124

1 Answers1

6

Why does the second version compile?

Because the second one doesn't create a Foo<int>::Bar<int>. You've run into the most vexing parse.

Had you attempted you use the b that appears to work, you'd have received further compiler errors showing that your b is in fact declared as a function.

Try this:

Foo<int>::Bar<int> b((Foo<char>::Bar<char>()));  // works fine? are you sure? :)
//                   ^                      ^

Your root problem is that the arguments will not be deduced, and you cannot provide them explicitly because we do not use function-call syntax per se when a constructor is invoked.


Why won't they be deduced?

To demonstrate, observe the following modified code where I replace the non-default constructor with a member function:

template <class T>
struct Foo
{

    template <class U>
    struct Bar
    {
        Bar();
        
        template <class X, class Y>
        void foo(typename Foo<X>::template Bar<Y> b)
        {}
    };
};

int main()
{
    //Foo<int>::Bar<int> b = Foo<char>::Bar<char>();
    Foo<int>::Bar<int> i;
    i.foo(Foo<char>::Bar<char>());
}

This gives us some more information to go on, wherein the key error is:

prog.cpp:11:14: note:   template argument deduction/substitution failed:
prog.cpp:23:30: note:   couldn't deduce template parameter ‘X’
  i.foo(Foo<char>::Bar<char>());

Changing the call, providing explicit arguments, to:

i.foo<char,char>(Foo<char>::Bar<char>());

yields a successful compilation; but this doesn't help us in our original code, as we cannot provide explicit arguments to a constructor invocation.

So, we're stuck with deduction, but unfortunately the nestedness breaks this for us through the following series of rules:

[C++11: 14.8.2.1/5]: These alternatives are considered only if type deduction would otherwise fail. If they yield more than one possible deduced A, the type deduction fails. [ Note: If a template-parameter is not used in any of the function parameters of a function template, or is used only in a non-deduced context, its corresponding template-argument cannot be deduced from a function call and the template-argument must be explicitly specified. —end note ]

[C++11: 14.8.2.5/5]: The non-deduced contexts are:

In short, we cannot expect the X in Foo<X>::Bar<Y> to be deduced, and that's where everything breaks down. So, basically, you can't do this. Sorry.

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • But what's the actual reason the code doesn't work? It really looks like it should... – Kerrek SB Oct 26 '13 at 22:20
  • Oh, right, it's just the usual non-deducible context, isn't it? It's the `X` you can't deduce, since indeed *any* `X` would do. – Kerrek SB Oct 26 '13 at 22:32
  • @KerrekSB: I still don't _quite_ get it; I think any `X` _won't_ do since `Foo::Bar` and, say, `Foo::Bar` are different types! – Lightness Races in Orbit Oct 26 '13 at 22:40
  • @KerrekSB: Okay, maybe I've got the wording now; it's down to the nestedness, though as above I'm not sure _why_. I would still prefer 100% normative text, too. :( – Lightness Races in Orbit Oct 26 '13 at 22:45
  • That quote sounds right. `Foo` is the nested-name-specifier, so `X` cannot be deduced. – Kerrek SB Oct 26 '13 at 22:49
  • @KerrekSB: Maybe it's an infeasible-complexity-of-implementation problem. – Lightness Races in Orbit Oct 26 '13 at 22:51
  • 1
    I think it's a simple sense-making problem. Imagine you have `template void foo(typename Bar::type);` and you call `foo(5)`. There's no sensible way to select a `T` for which `Bar::type` is `int`. The preimage relationship is not a functional one. – Kerrek SB Oct 26 '13 at 23:13
  • You wrote that you modified the original copy constructor. Please note that the original constructor is *not* a *copy* constructor. A templated constructor is *never* a copy constructor. (If you want to write a constructor taking any instance of the template class, write a templated constructor plus a copy constructor (taking the exact same type)). – leemes Oct 26 '13 at 23:19
  • @leemes: Ah, you're right, of course. I keep making that mistake in nomenclature, though I don't believe it affects the answer beyond trivialities. – Lightness Races in Orbit Oct 26 '13 at 23:25
  • @KerrekSB: Yeah, I suppose not, in the general case. In this specific case, it _could_ theoretically be deduced since `Foo::Bar` (for some known `X` and `Y`) is a concrete class with no ambiguities in specialisation. – Lightness Races in Orbit Oct 26 '13 at 23:28
  • @LightnessRacesinOrbit: I don't think I believe that: `Foo::Bar` is the same type as `Foo::Bar`, isn't it? – Kerrek SB Oct 26 '13 at 23:28
  • @KerrekSB: I don't think so? `[C++11: 9.7/1]:` *A class can be declared within another class. A class declared within another is called a nested class. **The name of a nested class is local to its enclosing class. The nested class is in the scope of its enclosing class.*** And recall that `Foo` and `Foo` are distinct classes. – Lightness Races in Orbit Oct 26 '13 at 23:29
  • @KerrekSB: Refer to http://stackoverflow.com/questions/19612378/nested-template-requires-explicit-construction/19612457?noredirect=1#comment29114325_19612457 :P – Lightness Races in Orbit Oct 26 '13 at 23:35