19

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.

jotik
  • 17,044
  • 13
  • 58
  • 123
  • if you know the abstract methods, you can do like [this](http://coliru.stacked-crooked.com/a/99ee522df4a1c7c7). if you want a fully generic solution, i think you are out of luck – sp2danny Mar 03 '16 at 16:53

4 Answers4

7

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

Community
  • 1
  • 1
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • @PiotrSkotnicki Quite clever, more than me for sure. :-) ... Completeley missed the `public` that breaks everything. Well, I wrote with the compiler on the mobile while waiting the bus to work, shame on me to have not checked it twice once at work indeed. Sorry. Anyway, also [that one](http://coliru.stacked-crooked.com/a/8b55cca1ee3d1176) doesn't work as expected, but should be `D(int)` not reachable? – skypjack Mar 04 '16 at 09:25
  • the problem is that your solution is intrusive in the sense that it imports base class' constructors into the scope of the derived class, so, they participate in overload resolution, which can bring about unexpected results as I showed. other than that, [your solution does work for other cases](http://coliru.stacked-crooked.com/a/7a7b48ef686cdc2a) – Piotr Skotnicki Mar 04 '16 at 09:29
  • @PiotrSkotnicki It works also for this case with a kind of tag dispatching (similar to what the allocator tag stands for), see http://coliru.stacked-crooked.com/a/df24001de4f3f925 - am I right? – skypjack Mar 04 '16 at 09:30
  • 1
    it's enough if derived class' constructor wins in overload resolution. in the case you presented, it's a function template, so base class' constructor is the preferred one – Piotr Skotnicki Mar 04 '16 at 09:32
  • @PiotrSkotnicki Ok, I'll update the answer, first of all with a thank you for you, than with the other example and a mention for our comments. Thank you as usual, really appreciated!! – skypjack Mar 04 '16 at 09:37
  • Well, I admit that almost all the solutions look like a workaround for a corner case of the standard, but it has been really an interesting discussion that helped me going deeper in the language. – skypjack Mar 04 '16 at 12:56
5

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;

};
jotik
  • 17,044
  • 13
  • 58
  • 123
  • 1
    Well, I explored also that solution, the problem here is that it is easy to do as far as you have one virtual method, but it's quite annoying when they grow in number, because you must define a new and completely useless class to be used only with the `noexcept` clause. Anyway, upvoted because still it's a viable solution. – skypjack Mar 05 '16 at 09:01
  • Yes, but I think it is a good trade-off for keeping the `Derived` constructor signature unchanged and clean. If the public interface of `Derived` would change (for the worse), it could be tedious for one to change all the constructor calls to take this workaround into account. – jotik Mar 05 '16 at 09:18
  • 3
    Another thing you can do is, don't bother to give implementations for the virtual functions at all, even dummy implementations. For purposes of an unevaluated context they only need to be declared -- and probably its better if they aren't defined at all. – Chris Beck Mar 05 '16 at 18:00
  • My original working implementation actually only had declarations. I'm not sure anymore why I added them to this example. :D Thanks for noticing, I changed the example to reflect this. – jotik Mar 05 '16 at 21:55
  • The problem with this solution is that you will duplicate fields from your base class, making memory footprint bigger than necessary. E.g.: add a `int foo;` in your base class and you will get a `Derived::foo` and a `Derived::MockDerived::foo` fields. – S.Clem Nov 06 '16 at 14:14
1

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();
}
Morwenn
  • 21,684
  • 12
  • 93
  • 152
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 1
    This counts as "modifying the abstract base class", but might be a workaround for some cases. All fields and functionality the previous `Base` constructor depends on, must be included in the `BaseBase` constructor or class, and everything depending on the pure virtual methods must stay in `Base`. If we consider pure method calls a bad thing, then it seems on first sight that such refactoring could actually be useful. But it still doesn't help in the general case. I'll give you an upvote for the effort thou. – jotik Mar 03 '16 at 19:34
  • @jotik You are right, absolutely, that's why I said *naïve, but working*. ;-) ... Anyway, really an interesting question, I'm still trying to figure out how to do it. – skypjack Mar 03 '16 at 19:44
  • @jotik I've added another answer that is more suitable, maybe you'll find it not a workaround. Let me know if it solves the problem. Really a nice question indeed. – skypjack Mar 04 '16 at 08:38
0

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.

Community
  • 1
  • 1
S.Clem
  • 491
  • 5
  • 10