Как выглядит определение (тело) унаследованного конструктора?

Из моего чтения ответов на SO и ссылка cppreference

Унаследованные конструкторы эквивалентны определяемым пользователем конструкторам с пустым телом и списком инициализаторов членов, состоящим из одного описателя вложенного имени, который перенаправляет все свои аргументы в конструктор базового класса.

Я пришел к выводу, что приведенные ниже классы D и E должны вести себя одинаково.

#include <string>
#include <utility>
using namespace std;

class B
{
public:
  B(string&& a) : a(move(a))
  {
  }

  string a;
};

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

class E : public B
{
public:
  E(string&& a) : B(a)
  {
  }
};

string foo()
{
  return "bar";
}

int main()
{
  D d = foo();//This compiles
  E e = foo();//This does not compile
  return 0;
}

E e = foo() правильно не компилируется, так как конструктор B принимает только string&&. Однако D d = foo() проходит нормально. Почему это? Используемый компилятор clang3.5.

РЕДАКТИРОВАТЬ: Кроме того, как объясняется в этом ответе, идеальная идиома пересылки не является заменой наследующим конструкторам. Итак, как именно выглядит тело?


person Pradhan    schedule 15.01.2015    source источник


Ответы (2)


Однако D d = foo() проходит нормально. Почему это?

Поскольку using B::B эффективно передает временную строку прямо в конструктор B, где ее все еще можно связать аля && (т. е. ее категория значений по-прежнему является xvalue), тогда любые дополнительные производные инициализация класса (если были другие элементы данных, VDT и т. д.). Это очень желательно, поскольку смысл использования конструкторов базового класса состоит в том, чтобы разрешить такое же использование клиента.

(Это контрастирует с E(string&&), внутри которого именованный параметр a больше не считается значением x (временно истекающим), готовым для перехода к B::B.)

(Если вы еще этого не сделали, вы можете взглянуть на (std::forward)[http://en.cppreference.com/w/cpp/utility/forward] тоже... это помогает с идеальной пересылкой аргументов)

person Tony Delroy    schedule 15.01.2015
comment
Как бы выглядело это определение? В вопросе был только один пример. Но что, если, скажем, у D есть другие члены с конструкторами по умолчанию? Когда ссылка cppreference говорит вперед, подразумевается ли это на самом деле использование универсальной ссылки + std::forward идиомы? Если да, то как обойти проблемы, описанные здесь? - person Pradhan; 15.01.2015
comment
Как будет выглядеть определение? - не будет... любое мысленное приближение, которое вы делаете, ошибочно, как объяснено в проблемах, изложенных здесь, ссылка, которую вы только что разместили, где упоминается проблема явность конструкторов. Вместо того, чтобы пытаться мысленно смоделировать производный конструктор как код C++, вызывающий деструктор базового класса, я предлагаю вам представить открытый конструктор базового класса как доступный через интерфейс производного класса с некоторыми потенциальными скрытыми предварительными и/или последующими шагами для инициализация других баз и членов, VDT и т.д.. - person Tony Delroy; 15.01.2015

Формулировка из стандарта о том, как выглядит определение унаследованного конструктора в производном классе, немного более явное, чем то, на что намекает описание cppreference, но ключевая фраза в последнем — пересылает все свои аргументы< /эм>. Другими словами, категория значений аргументов сохраняется, чего не делает ваше определение E, и, следовательно, не удается скомпилировать.

Из N3337, §12.9/8 [class.inhctor]

... Неявно определенный наследующий конструктор выполняет набор инициализаций класса, который был бы выполнен написанным пользователем встроенным конструктором для этого класса с mem-initializer-list, чей единственный mem-initializer-list Initializer имеет mem-initializer-id, который называет базовый класс, указанный в описателе вложенного имени в декларации использования и список-выражений, как указано ниже, и где составной оператор в теле функции пуст (12.6.2). Если этот пользовательский конструктор будет иметь неправильный формат, программа будет неправильно сформирована. Каждое выражение в списке-выражений имеет форму static_cast<T&&>(p), где p — это имя соответствующего параметра конструктора, а T — объявленный тип p.

Таким образом, аргументы конструктора идеально передаются соответствующему унаследованному конструктору (указано, что std::forward (§20.2.3) возвращает static_cast<T&&>(p), точно так же, как в описании выше). В зависимости от объявленного типа параметра конструктора происходит свертывание ссылок, как описано в этом ответе.

В вашем случае [T=string&&] и приведение снова дает string&&, что может быть привязано к параметру B. Чтобы соответствовать этому поведению в E, вы должны переписать конструктор как

E(string&& a) : B(static_cast<string&&>(a))
{
}
person Praetorian    schedule 15.01.2015
comment
Извините, если я упустил что-то очевидное, но выделенная часть стандартной цитаты звучит идентично подходу, который ссылка в вопросе считает недостаточной. - person Pradhan; 15.01.2015
comment
@Pradhan Этого недостаточно, если вы будете следовать этому описанию и реализовать конструктор пересылки, вам нужно будет пометить его explicit вручную, в отличие от объявления использования, которое наследует явность базового конструктора, если таковая имеется. Стандарт не учитывает это, когда он сравнивает поведение унаследованного конструктора с поведением предоставленного пользователем. - person Praetorian; 15.01.2015