Determine whether a constructor of an abstract base class is noexcept?

偶尔善良 提交于 2019-12-03 04:58:53
skypjack

[UPDATE: IT'S WORTH JUMPING TO THE EDIT SECTION]

Ok, I found a solution, even though it doesn't compile with all the compilers because of a bug in GCC (see this question for further details).

The solution is based on inherited constructors and the way functions calls are resolved.
Consider the following example:

#include <utility>
#include <iostream>

struct B {
    B(int y) noexcept: x{y} { }
    virtual void f() = 0;
    int x;
};

struct D: public B {
private:
    using B::B;

public:
    template<typename... Args>
    D(Args... args)
    noexcept(noexcept(D{std::forward<Args>(args)...}))
        : B{std::forward<Args>(args)...}
    { }

    void f() override { std::cout << x << std::endl; }
};

int main() {
    B *b = new D{42};
    b->f();
}

I guess it's quite clear.
Anyway, let me know if you find that something needs more details and I'll be glad to update the answer.
The basic idea is that we can inherit directly the noexcept definition from the base class along with its constructors, so that we have no longer to refer to that class in our noexcept statements.

Here you can see the above mentioned working example.

[EDIT]

As from the comments, the example suffers of a problem if the constructors of the base class and the derived one have the same signature.
Thank to Piotr Skotnicki for having pointed it out.
I'm going to mention those comments and I'll copy and paste the code proposed along with them (with a mention of the authors where needed).

First of all, here we can see that the example as it is does not work as expected (thanks to Piotr Skotnicki for the link).
The code is almost the same previously posted, so it doesn't worth to copy and paste it here.
Also, from the same author, it follows an example that shows that the same solution works as expected under certain circumstances (see the comments for furter details):

#include <utility>
#include <iostream>

struct B {
    B(int y) noexcept: x{y}
    {
        std::cout << "B: Am I actually called?\n";
    }
    virtual void f() = 0;
    int x;
};

struct D: private B {
private:
    using B::B;

public:
    template<typename... Args>
    D(int a, Args&&... args)
    noexcept(noexcept(D{std::forward<Args>(args)...}))
        : B{std::forward<Args>(args)...}
    {
        std::cout << "D: Am I actually called?\n";
    }
    void f() override { std::cout << x << std::endl; }
};

int main()
{
    D* d = new D{71, 42};
    (void)d;
}

Also, I propose an alternative solution that is a bit more intrusive and is based on the idea for which the std::allocator_arg_t stands for, that is also the same proposed by Piotr Skotnicki in a comment:

it's enough if derived class' constructor wins in overload resolution.

It follows the code mentioned here:

#include <utility>
#include <iostream>

struct B {
    B(int y) noexcept: x{y}
    {
        std::cout << "B: Am I actually called?\n";
    }
    virtual void f() = 0;
    int x;
};

struct D: public B {
private:
    using B::B;

public:
    struct D_tag { };

    template<typename... Args>
    D(D_tag, Args&&... args)
    noexcept(noexcept(D{std::forward<Args>(args)...}))
        : B{std::forward<Args>(args)...}
    {
        std::cout << "D: Am I actually called?\n";
    }
    void f() override { std::cout << x << std::endl; }
};

int main()
{
    D* d = new D{D::D_tag{}, 42};
    (void)d;
}

Thanks once more to Piotr Skotnicki for his help and comments, really appreciated.

Based on skypjack's answer a better solution which does not require a signature change of the Derived constructor would be to define a mock subclass of Base as a private type member of Derived, and use the construction of that in the Derived constructor noexcept specification:

class Derived: Base {

private:

    struct MockDerived: Base {
        using Base::Base;

        // Override all pure virtual methods with dummy implementations:
        int f() override; // No definition required
    };

public:

    template <typename ... Args>
    Derived(Args && ... args)
            noexcept(noexcept(MockDerived(std::forward<Args>(args)...)))
        : Base(std::forward<Args>(args)...)
        , m_f(g())
    {}

    int f() override { return 42; } // Real implementation

    int m_f;

};
skypjack

A naive but working example would be to introduce a non-virtual base class and export its constructor by means of the using directive. Here is an example:

#include<utility>

struct BaseBase {
    BaseBase() noexcept { }
};

struct Base: public BaseBase {
    using BaseBase::BaseBase;
    virtual int f() = 0;
};

struct Derived: public Base {
    template <typename ... Args>
    Derived(Args && ... args)
        noexcept(noexcept(BaseBase(std::forward<Args>(args)...)))
        : Base(std::forward<Args>(args)...)
    { }

    int f() override { }
};

int main() {
    Derived d;
    d.f();
}
S.Clem

I was facing the same problem and the solution I found was to implement additionnal traits.

You can have a look to my post here.

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