6

Although both of the following compile (with Visual Studio 2013), is one of them more "correct" with respect to C++ idioms? I speak particularly with respect to explicit template parameters when calling base class constructors and declaring members. Does The Standard have a view on this? Is there a good practical reason to prefer one over the other?

template<class T>
class Bar1 : public Base<T>
{
public:

    Bar1() : Base() {}
    Bar1(T value) : Base(value) {}
    Bar1(Bar1 const & other) : Base(other.value) {} 

    void Foo(Bar1 const & other)
    {
        // Some foo related activity.
    }
};

template<class T>
class Bar2 : public Base<T>
{
public:

    Bar2() : Base<T>() {}
    Bar2(T value) : Base<T>(value) {}
    Bar2(Bar2<T> const & other) : Base<T>(other.value) {}

    void Foo(Bar2<T> const & other)
    {
        // Some foo related activity.
    }
};
anatolyg
  • 26,506
  • 9
  • 60
  • 134
Robinson
  • 9,666
  • 16
  • 71
  • 115
  • 1
    If I'm not mistaken, only `Bar2` is syntactically correct. What compiler did you use and did you actually call all the constructors of `Bar1`? – MikeMB Jul 02 '15 at 07:16
  • 1
    The first form shouldn't work, because it trys to refer to the injected-class-name (which acts like a public member) of a dependent base class. You must be using MSVC, which doesn't correctly implement the name lookup rule. – cpplearner Jul 02 '15 at 07:19
  • It's Visual Studio 2013. I made a simple repo and it compiles and everything is as expected (working correctly). Well that's interesting cpplearner. The bad habits we pick up because of things like this. – Robinson Jul 02 '15 at 07:20
  • 4
    Then it's one of the cases, where MSVC++ is able to parse non standard conformant template code. `Bar2` is the better (only) way to go. – MikeMB Jul 02 '15 at 07:24
  • 1
    @cpplearner You should turn this into an answer. – Angew is no longer proud of SO Jul 02 '15 at 07:53
  • 1
    Related: http://stackoverflow.com/q/2974780/509868 – anatolyg Jul 02 '15 at 10:07
  • 1
    It would seem that `Base` has to spelled out, but `Bar2` can be used instead of `Bar2` without being qualified with `` – Glenn Teitelbaum Jul 02 '15 at 19:31

2 Answers2

2

This question relies on something called injected-class-name. From [class]

A class-name is inserted into the scope in which it is declared immediately after the class-name is seen. The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name. For purposes of access checking, the injected-class-name is treated as if it were a public member name.

And from [temp.local]:

Like normal (non-template) classes, class templates have an injected-class-name (Clause 9). The injected-class-name can be used as a template-name or a type-name. When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type-specifier of a friend class template declaration, it refers to the class template itself. Otherwise, it is equivalent to the template-name followed by the template-parameters of the class template enclosed in <>.

That is, within the definitions Bar1<T> or Bar2<T>, you can use Bar1 or Bar2 to refer to the full class type. That is, these declarations are equivalent:

void Foo(Bar2<T> const & other);
void Foo(Bar2 const & other);

However, the rules for lookup apply as normal. While there is an injected-class-name for Base, that is a dependent name and so cannot be found via normal unqualified lookup. From [temp.dep]:

In the definition of a class or class template, the scope of a dependent base class (14.6.2.1) is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.

which makes this:

Bar1() : Base() {}

ill-formed. Base is unqualified lookup, and there is no such name Base. There is a Base<T>::Base (the injected-class-name there), but that scope is unexamined. You would have to either do qualified lookup:

Bar1() : Bar1<T>::Base() {}
Bar1() : Bar1::Base() { }

or not rely on the injected-class-name of Base:

Bar1() : Base<T>() { }

VS is wrong in accepting Bar1. Bar2 is perfectly OK, if more verbose than strictly possible. Nothing wrong with that.

Worth noting also that if the base weren't dependent, you could still use its injected-class-name even if it were a template:

template <class T> struct Base { };

struct Derived : Base<int> {
    Derived() : Base() { } // OK, lookup finds Base<int>::Base
};
Barry
  • 286,269
  • 29
  • 621
  • 977
1

The first form is incorrect and should not compile. To see why, consider what would happen if you multiply inherited from Base<Foo> and Base<Bar> (or Base<T> and Base<Base<T>> or whatever). How could it resolve the ambiguity?

The idea that the first form would work may be derived from mistaken application of the rule that, within a class template definition, you don't need to provide the template param(s) when mentioning the class name. This doesn't apply to a templated base class, though.

Snargleplax
  • 185
  • 1
  • 5