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

此生再无相见时 提交于 2019-12-03 16:27:06

问题


In C++11 and later, how to determine whether a constructor of an abstract base class is noexcept? The following methods don't work:

#include <new>
#include <type_traits>
#include <utility>

struct Base { Base() noexcept; virtual int f() = 0; };

// static assertion fails, because !std::is_constructible<Base>::value:
static_assert(std::is_nothrow_constructible<Base>::value, "");

// static assertion fails, because !std::is_constructible<Base>::value:
static_assert(std::is_nothrow_default_constructible<Base>::value, "");

// invalid cast to abstract class type 'Base':
static_assert(noexcept(Base()), "");

// invalid new-expression of abstract class type 'Base'
static_assert(noexcept(new (std::declval<void *>()) Base()), "");

// cannot call constructor 'Base::Base' directly:
static_assert(noexcept(Base::Base()), "");

// invalid use of 'Base::Base':
static_assert(noexcept(std::declval<Base &>().Base()), "");

A simple use for this would be:

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

    int f() override;

    int m_f;
};

Any ideas about how to archieve this or whether it is possible at all without modifying the abstract base class?

PS: Any references to ISO C++ defect reports or work-in-progress is also welcome.

EDIT: As was pointed out twice, defaulting the Derived constructors with = default makes noexcept being inherited. But this does not solve the problem for the general case.


回答1:


[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.




回答2:


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;

};



回答3:


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



回答4:


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.



来源:https://stackoverflow.com/questions/35777287/determine-whether-a-constructor-of-an-abstract-base-class-is-noexcept

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