c++language-lawyertypenameusing-declarationinheriting-constructors

C++ using declaration with typename in inheriting-constructors


While reading this question, I found a strange point:

template <typename T>
class Subclass : public Baseclass<T>
{
public:
    using typename Baseclass<T>::Baseclass;
    //    ^^^^^^^^
};

Since typename, Baseclass<T>::Baseclass should be injected class name, not a constructor. As far as I know, it's the same case as this:

template <typename T>
class Base
{
public:
    typedef short some_type;
};

template <typename T>
class Sub : public Base<T>
{
public:
    using typename Base<T>::some_type;
};

To make sure, I wrote a test code.

#include <iostream>

template <typename T>
class Base
{
public:
    Base() { std::cout << "A::A()\n"; }
    Base(int) { std::cout << "A::A(int)\n"; }
    Base(const char *) { std::cout << "A::A(const char *)\n"; }
};

template <typename T>
class Sub : public Base<T>
{
    using typename Base<T>::Base;
};

int main()
{
    Sub<char> s1;
    Sub<char> s2(3);
    Sub<char> s3("asdf");
}

However, it runs on gcc 4.8.3.

$ g++ -std=c++1y -Wall -Wextra -Werror -pedantic test.cpp -o test && ./test
A::A()
A::A(int)
A::A(const char *)

It also runs without typename.

$ cat test.cpp
...
    using Base<T>::Base;
...

$ g++ -std=c++1y -Wall -Wextra -Werror -pedantic test.cpp -o test && ./test
A::A()
A::A(int)
A::A(const char *)

Why did I get these results? What did I miss?


Solution

  • The standard is pretty clear about this ([namespace.udecl]/1)

    using-declaration:

    using typename_opt nested-name-specifier unqualified-id ;

    The typename keyword is therefore an optional part of a using declaration that may appear even for using declarations of non-types. The following code should therefore be standard conformant:

    template < typename T > class Base {
      protected:
        typedef T Ttype;
        Ttype member;
    
      public:
        Base() {
            std::cout << "A::A()\n";
        }
        Base(int) {
            std::cout << "A::A(int)\n";
        }
        Base(const char *) {
            std::cout << "A::A(const char *)\n";
        }
    
      protected:
        void memfunc(void) {
            std::cout << "A::memfunc(void)\n";
        }
    };
    
    template< typename T >
    struct SubNoTypename : protected Base< T > {
        using Base< T >::Base;
        using Base< T >::member;
        using Base< T >::memfunc;
        using Base< T >::Ttype;  // n.b. no error in clang++
    };
    
    template< typename T >
    struct SubTypename : protected Base< T > {
        using typename Base< T >::Base;    // error in clang++
        using typename Base< T >::member;  // error in clang++
        using typename Base< T >::memfunc; // error in clang++
        using typename Base< T >::Ttype;
    };
    

    Both SubNoTypename and SubTypename are recognized as standard conformant by gcc. On the other hand clang++ complains in SubTypename about malplaced typename keywords. However, this is not even consistent, because it should then complain about a missing typename in using Base< T >::Ttype;. This clearly is a clang bug.


    Edit The typename keyword is also allowed if the base class is no template class, a place where ordinarily you would never expect this keyword to be valid:

    class BaseNoTemplate {
      protected:
        typedef T Ttype;
        Ttype member;
    
      public:
        BaseNoTemplate() {
            std::cout << "A::A()\n";
        }
        BaseNoTemplate(const char *) {
            std::cout << "A::A(const char *)\n";
        }
    
        void memfunc(void) {
            std::cout << "A::memfunc(void)\n";
        }
    };
    
    struct SubNoTemplateNoTypename : protected BaseNoTemplate {
        using BaseNoTemplate::BaseNoTemplate;
        using BaseNoTemplate::member;
        using BaseNoTemplate::memfunc;
        using BaseNoTemplate::Ttype;
    };
    
    struct SubNoTemplateTypename : protected BaseNoTemplate {
        using typename BaseNoTemplate::BaseNoTemplate; // error in clang++
        using typename BaseNoTemplate::member;  // error in clang++
        using typename BaseNoTemplate::memfunc; // error in clang++
        using typename BaseNoTemplate::Ttype;   // n.b. no error in clang++
    };