Как выглядит определение (тело) унаследованного конструктора?
Из моего прочтения ответов на 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] тоже... это помогает с идеальной передачей аргументов)