Does inheriting constructors work with templates i

2019-02-02 23:27发布

问题:

In C++0x, you can use the using keyword to inherit constructors, like so:

class B { B(int) {} };

class A : public B { using B::B; };

Which will implicitly declare an A(int) constructor. Does this work with templates?

class B { B(int) {} };

template<class T> class A : public T { using T::T; };

Within T::T, I expect the compiler to figure out the left hand T since using the scope operator on template arguments is normal, but figuring out that the right hand T is the constructor is a special case. In fact it appears there's an ambiguity: what if I have a method called T in B that I'm trying to add overloads to in A (that's how a compiler would interpret such a using declaration pre-C++0x)?

回答1:

Yes it works, and the reason is the name lookup mechanism. The mechanism a inheriting-constructors declaration works is simple: If the using declaration's name refers to base class constructors, that's an inheriting constructors declaration. At 3.4.3.1[class.qual]p2 we find:

In a lookup in which the constructor is an acceptable lookup result and the nested-name-specifier nominates a class C

  • if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause 9), or
  • in a using-declaration (7.3.3) that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier

the name is instead considered to name the constructor of class C.

This is the paragraph that makes out of class constructor definitions work, and this is also the paragraph that makes inheriting constructor declarations work. The second bullet applies in this case:

struct B {
  B(int) { }
};

typedef B mytype;

struct A : B {
  // "name ... is the same as the identifier ... in the last component ..."
  using mytype::mytype;
};


template<typename T> using same = T;

struct C : B {
  // "name ... is the same as the template-name ... in the last component ..."
  same<B>::same;
};

The latter example proves also useful in cases such as the following

template<template<typename> class Base>
struct X : Base<int> {
  using Base<int>::Base;
};

In summary:

  • The first bullet above is a semantic rule - if the name after the nested name specifier refers to the injected class name (B::B or mytype::B), then it will be translated to refer to the constructor(s).

  • The second bullet is a syntactic rule - the names just must match - their meaning is irrelevant otherwise - there could have been a member called Base in the template argument provided to X, such as in the following, but the using declaration would still import the constructors and do not name the member Base:

    template<typename T> struct D { private: T Base; };
    X<D> x; // valid, the private member is *not* touched!
    


回答2:

Yes, it appears it does, from the standard (Feb 2011 Draft), section 12.9:

template< class T >
struct D : T {
using T::T; // declares all constructors from class T
~D() { std::clog << "Destroying wrapper" << std::endl; }
};

Class template D wraps any class and forwards all of its constructors, while writing a message to the standard log whenever an object of class D is destroyed. —end example

Another thing to note, while the standard allows it, according to this list, only 1 compiler, IBM XLC++, supports this feature in a release version. GCC only currently supports it with a patch.

Edit: AJG85 pointed out that the T in the template always refers to the placeholder, so the 'using T::T' always refers to the template argument.