Я пытаюсь создать конструктор, подобный именованным аргументам, для некоторых классов проекта.
Я делаю это, определяя прокси класса, который будет содержать аргументы и передавать экземпляр этого прокси конструктору моих классов.
Все работало нормально, пока мне не пришлось получить один из моих классов.
По сути, я подумал: я собираюсь получить новый прокси производного класса от прокси базового класса. Это тоже работает, но только если я использую только аргументы производного класса прокси.
Вот пример, так как это легче понять:
class Person
{
public:
class PersonArgs
{
public:
const std::string& Name() const { return _name; }
PersonArgs& Name(const std::string& name)
{
_name = name;
return *this;
}
const std::string& Surname() const { return _surname; }
PersonArgs& Surname(const std::string& surname)
{
_surname = surname;
return *this;
}
protected:
std::string _name;
std::string _surname;
}
public:
Person()
: _name("")
, _surname("")
{ }
Person(const PersonArgs& args)
: _name(args.Name())
, _surname(args.Surname())
{ }
protected:
std::string _name;
std::string _surname;
}
class PersonEx : public Person
{
public:
class PersonExArgs : public Person::PersonArgs
{
public:
const std::string& Address() const { return _address; }
PersonExArgs& Address(const std::string& address)
{
_address = address;
return *this;
}
protected:
std::string _address;
}
public:
PersonEx()
: _address("")
{ }
PersonEx(const PersonExArgs& args)
: Person(args)
, _address(args.Address())
{ }
protected:
std::string _address;
}
int main(int argc, char** argv)
{
// This is ok since PersonExArgs::Address returns a PersonExArgs&
PersonEx* p1 = new PersonEx(PersonEx::PersonExArgs().Address("example"));
// This won't work since PersonExArgs::Name returns a PersonArgs&
PersonEx* p2 = new PersonEx(PersonEx::PersonExArgs().Address("example").Name("Mark"));
}
По сути, поскольку я приковываю аргументы, возвращающие ссылку на экземпляр прокси-класса, когда я устанавливаю аргумент, он прерывается при использовании этого из производного прокси-класса, так как он возвращает ссылку на базовый прокси-класс, а не на производный, что не позволяет мне получить доступ к производным аргументам прокси и не передавать его конструктору производного класса.
У кого-нибудь есть идея, как это исправить?
Может быть ковариантные типы возврата это то, что вы ищете.
Увидеть Вот Больше подробностей.
Вы можете определить PersonArgs
как (примечание virtual
ключевые слова расположены вокруг):
class PersonArgs
{
public:
const std::string& Name() const { return _name; }
virtual PersonArgs& Name(const std::string& name)
{
_name = name;
return *this;
}
const std::string& Surname() const { return _surname; }
virtual PersonArgs& Surname(const std::string& surname)
{
_surname = surname;
return *this;
}
protected:
std::string _name;
std::string _surname;
};
Затем определите PersonExArgs
как (примечание override
и ковариантный тип возврата):
class PersonExArgs : public Person::PersonArgs
{
public:
const std::string& Address() const { return _address; }
PersonExArgs& Address(const std::string& address)
{
_address = address;
return *this;
}
PersonExArgs& Name(const std::string& name) override
{
PersonArgs::Name(name);
return *this;
}
PersonExArgs& Surname(const std::string& surname) override
{
PersonArgs::Surname(surname);
return *this;
}
protected:
std::string _address;
};
Вероятно, это раздражает, потому что вам приходится переопределять каждую функцию в базовом классе, но это хорошо работает.
Смотрите это и работает на wandbox.
Наиболее распространенным решением этой проблемы является шаблон CURLY Recurring Template Pattern (CRTP):
template <typename Derived>
class PersonArgs
{
public:
const std::string& Name() const { return _name; }
Derived& Name(const std::string& name)
{
_name = name;
return static_cast<Derived&>(*this);
}
const std::string& Surname() const { return _surname; }
Derived& Surname(const std::string& surname)
{
_surname = surname;
return static_cast<Derived&>(*this);
}
protected:
std::string _name;
std::string _surname;
};
...
class PersonExArgs : public Person::PersonArgs<PersonExArgs>
{
public:
const std::string& Address() const { return _address; }
PersonExArgs& Address(const std::string& address)
{
_address = address;
return *this;
}
protected:
std::string _address;
};
В вашем случае вы можете объединить его с другим базовым классом для очистки интерфейса:
class Person {
class PersonArgsBase
{
public:
const std::string& Name() const { return _name; }
const std::string& Surname() const { return _surname; }
protected:
std::string _name;
std::string _surname;
};
template <typename Derived>
class PersonArgs : public PersonArgsBase
{
Derived& Name(const std::string& name)
{
_name = name;
return static_cast<Derived&>(*this);
}
Derived& Surname(const std::string& surname)
{
_surname = surname;
return static_cast<Derived&>(*this);
}
};
...
};
class PersonEx {
class PersonExArgs : public Person::PersonArgs<PersonExArgs>
{
...
};
};