Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ idioms when declaring template class members and constructors

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.
    }
};
like image 840
Robinson Avatar asked Jul 02 '15 07:07

Robinson


People also ask

Which is correct example of template parameters?

For example, given a specialization Stack<int>, “int” is a template argument. Instantiation: This is when the compiler generates a regular class, method, or function by substituting each of the template's parameters with a concrete type.

Can constructors be templated?

As long as you are satisfied with automatic type inference, you can use a template constructor (of a non-template class). @updogliu: Absolutely.

How do you define a template member function?

You may define a template member function outside of its class template definition. When you call a member function of a class template specialization, the compiler will use the template arguments that you used to generate the class template.

What are template arguments?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.


1 Answers

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
};
like image 80
Barry Avatar answered Nov 15 '22 05:11

Barry