Реализация именованных аргументов в 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"));
}

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

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

2 ответа

Возможно, вы ищете ковариантные типы возврата.
Смотрите здесь для более подробной информации.


Вы можете определить 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>
    {
        ...
    };
};
Другие вопросы по тегам