С++ с использованием объявления с именем типа в конструкторах наследования

Читая этот вопрос, я обнаружил странный момент:

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

Поскольку typename, Baseclass<T>::Baseclass должно быть внедренным именем класса, а не конструктор. Насколько я знаю, это тот же случай, что и этот:

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;
};

Чтобы убедиться, я написал тестовый код.

#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");
}

Однако он работает на 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 *)

Он также работает без 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 *)

Почему я получил такие результаты? Что я пропустил?


person ikh    schedule 20.09.2014    source источник
comment
clang++ отклоняет typename.   -  person dyp    schedule 20.09.2014
comment
Я слишком заботился о своем здравомыслии, чтобы ответить на другой вопрос... Стандарт говорит в [namespace.udecl]/1. Если using-declaration называет конструктор, он неявно объявляет набор конструкторы в классе, в котором появляется using-declaration; в противном случае имя, указанное в using-declaration, является синонимом имени некоторой сущности, объявленной в другом месте. Но в [class.ctor]/1 конструкторы не имеют имен.   -  person dyp    schedule 20.09.2014
comment
Обратите внимание, что существует [namespace.udecl]/20. Если объявление-использования использует ключевое слово typename и указывает зависимое имя, имя, представленное объявлением-использования, рассматривается как имя-определения-типа.   -  person dyp    schedule 20.09.2014
comment
@dyp О, копирование и вставка делают ту же опечатку ;; Во всяком случае, кажется, что я и clang правы; gcc, похоже, имеет некоторые ошибки в стандарте детализации.   -  person ikh    schedule 20.09.2014
comment
Правило состоит в том, что при поиске, в котором имена функций не игнорируются, а описатель вложенного имени назначает класс C: — если имя указано после описателя вложенного имени, при поиске в C, является внедренным именем класса C (пункт 9) [...] вместо этого имя считается именем конструктора класса C. ([class .qual]/p2)   -  person T.C.    schedule 20.09.2014


Ответы (2)


Стандарт довольно ясно об этом говорит ([namespace.udecl]/1)

использование-объявление:

использование typename_opt спецификатора вложенного имени unqualified-id ;

Таким образом, ключевое слово typename является необязательной частью объявления использования, которое может появляться даже при использовании объявлений нетипов. Поэтому следующий код должен соответствовать стандарту:

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;
};

И SubNoTypename, и SubTypename признаются gcc соответствующими стандарту. С другой стороны clang++ жалуется в SubTypename на неуместные typename ключевые слова. Однако это даже несовместимо, потому что тогда он должен жаловаться на отсутствие typename в using Base< T >::Ttype;. Это явно баг лязга.


Редактировать Ключевое слово typename также разрешено, если базовый класс не является классом-шаблоном, где обычно вы никогда не ожидаете, что это ключевое слово будет допустимым:

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++
};
person user1978011    schedule 30.04.2015
comment
Спасибо за ответ на мой старый вопрос без ответа! Проблема была решена, но я не могу не задаться вопросом, почему стандарт разрешает typename_opt внутри объявления using - мне кажется, это не нужно :) - person ikh; 30.04.2015
comment
Извините, но это не имеет никакого смысла. Тот факт, что грамматика допускает использование ключевого слова typename для нетипов, объясняется просто тем, что ограничение на использование typename для типов является семантическим, а не синтаксическим. (Но если вы также устраните семантические ограничения, если таковые имеются, это может стать хорошим ответом.) - person ; 30.04.2015
comment
@hvd Я добавил несколько примеров, показывающих использование имени типа для базовых классов, не являющихся шаблонами. Имхо, это синтаксический вопрос, а не семантический. - person user1978011; 30.04.2015
comment
Не имеет смысла рассматривать синтаксис без учета семантики. void main(int arg, char *arg[-1]) { return "Hello, world!" % 1.23; } является синтаксически допустимым, но я не думаю, что кто-то будет утверждать, что из-за этого он соответствует стандарту. - person ; 01.05.2015
comment
Нет, using Base< T >::Ttype; выдается как ошибка в Clang - person xmh0511; 04.08.2021
comment
Кроме того, я думаю, что у using Base< T >::Ttype; здесь нет проблем. В стандарте только говорится: Если декларатор использования использует ключевое слово typename и указывает зависимое имя ([temp.dep]), имя, представленное объявлением использования, рассматривается как имя typedef. В Другими словами, ключевое слово typename, используемое в деклараторе использования, указывает, что имя относится к типу. Если бы мы не использовали ключевое слово, было бы здесь нарушено какое-либо правило? - person xmh0511; 04.08.2021

Извините, а почему вам нужно using, а не просто typedef?

template <typename T>
class Sub : public Base<T>
{
    typedef Base<T> Base;
};
person Nickolay Merkin    schedule 02.12.2014
comment
Вы должны искать конструкторы, наследующие С++ :) - person ikh; 03.12.2014