Реализация именованных аргументов в C ++ с производными классами

Я пытаюсь создать конструктор, подобный именованным аргументам, для некоторых классов проекта.

Я делаю это, определяя прокси класса, который будет содержать аргументы и передавать экземпляр этого прокси конструктору моих классов.

Все работало нормально, пока мне не пришлось получить один из моих классов.

По сути, я подумал: я собираюсь получить новый прокси производного класса от прокси базового класса. Это тоже работает, но только если я использую только аргументы производного класса прокси.

Вот пример, так как это легче понять:

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

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

У кого-нибудь есть идея, как это исправить?

4

Решение

Может быть ковариантные типы возврата это то, что вы ищете.
Увидеть Вот Больше подробностей.


Вы можете определить 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.

1

Другие решения

Наиболее распространенным решением этой проблемы является шаблон 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>
{
...
};
};
1

По вопросам рекламы [email protected]