C++ named arguments implementation with derived classes

孤者浪人 提交于 2019-12-22 08:32:16

问题


I'm trying to create a named-arguments-like constructor for some of the classes of a project.

The way I'm doing it is by defining a class proxy which will hold the arguments and pass an instance of this proxy to the constructor of my classes.

Everything worked fine until I had to derive one of my classes.

Basically I thought: I'm gonna derive the new derived class proxy from the base class proxy. This works too, but only if I use only the derived proxy class arguments.

Here is an example since it is easier to understand:

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

Basically, since I chain the arguments returning a reference to the proxy class instance when I set an argument, it breaks when using this from a derived proxy class since it will return a reference to the base proxy class and not the derived one, not allowing me to access the derived proxy arguments and neither pass it to the constructor of the derived class.

Anyone has an idea on how to fix this?


回答1:


Maybe covariant return types are what you are looking for.
See here for more details.


You can define PersonArgs as (note virtual keywords placed around):

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

Then define PersonExArgs as (note override and the covariant return type):

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

Probably it's annoying for you have to override each and every function in the base class but it does the work nicely.
See it up and running on wandbox.




回答2:


The most common solution to this problem is the Curiously 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;
};

In your case, you could combine it with another base class to clean up the interface:

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>
    {
        ...
    };
};


来源:https://stackoverflow.com/questions/46288075/c-named-arguments-implementation-with-derived-classes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!