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

Из моего прочтения ответов на 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.

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

2 ответа

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

От N3337, §12.9 / 8 [class.inhctor]

... Неявно определяемый наследующий конструктор выполняет набор инициализаций класса, который будет выполнен встроенным конструктором, написанным пользователем для этого класса с помощью mem-initializer-list, чей единственный mem-initializer имеет идентификатор mem-initializer-id, который называет базовый класс, обозначенный в спецификаторе nested-name в объявлении using и в списке выражений, как указано ниже, и где составной оператор в его теле функции пуст (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))
{
}

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

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

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

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

Другие вопросы по тегам