Определите, является ли конструктор абстрактного базового класса noexcept?


В C++11 и более поздних версиях, как определить, является ли конструктор абстрактного базового класса noexcept? Следующие методы не работают:

#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()), "");

Простое использование для этого было бы:

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

Есть ли идеи о том, как это архивировать или возможно ли вообще без изменения абстрактного базового класса?

PS: любые ссылки на отчеты о дефектах ISO C++ или незавершенном производстве также приветствуются.

EDIT: как было указано дважды, по умолчанию Derived конструкторы с = default делают noexcept наследуемыми. Но это не решает проблему для общего случая.

4 18

4 ответа:

[ОБНОВЛЕНИЕ: СТОИТ ПЕРЕЙТИ К РАЗДЕЛУ EDIT

Хорошо, я нашел решение, хотя оно не компилируется со всеми компиляторами из-за ошибки в GCC (смотрите Этот вопрос для более подробной информации).

Решение основано на унаследованных конструкторах и способе разрешения вызовов функций.
Рассмотрим следующий пример:
#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();
}

Я думаю, это совершенно ясно.
В любом случае, дайте мне знать, если вы обнаружите, что что-то нуждается в большем подробности и я буду рад обновить ответ.
Основная идея заключается в том, что мы можем непосредственно наследовать определение noexcept от базового класса вместе с его конструкторами, так что нам больше не нужно ссылаться на этот класс в наших операторах noexcept.

Здесь вы можете увидеть вышеупомянутый рабочий пример.

[EDIT]

Как и из комментариев, пример страдает от проблемы, если конструкторы базового класса и производного имеют одинаковое значение. подпись.
Спасибо Петру Скотницкому, что указал на это.
Я собираюсь упомянуть эти комментарии, и я скопирую и вставлю предложенный код вместе с ними (с упоминанием авторов, где это необходимо).

Прежде всего, здесь мы видим, что пример как он есть не работает так, как ожидалось (спасибо Петру Скотницкому за ссылку).
Код почти такой же, как и ранее, поэтому его не стоит копировать и вставлять сюда.
Кроме того, от того же автора, далее следует пример , который показывает, что одно и то же решение работает, как и ожидалось, при определенных обстоятельствах (см. комментарии для деталей furter):

#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;
}
Кроме того, я предлагаю альтернативное решение, которое немного более навязчиво и основано на идее, за которую выступает std::allocator_arg_t, которая также предложена Петром Скотницким в комментарии:

Достаточно, если конструктор производного класса выигрывает в разрешении перегрузки.

Он следует коду упоминается здесь :

#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;
}
Еще раз спасибо Петру Скотницкому за помощь и комментарии, очень благодарен.

На основе ответаskypjack лучшим решением, не требующим изменения сигнатуры конструктора Derived, было бы определение фиктивного подкласса Base как частного члена типа Derived и использование его конструкции в спецификации Derived конструктора noexcept:

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;

};

Наивным, но работающим примером было бы введение невиртуального базового класса и экспорт его конструктора с помощью директивы using. Вот пример:

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

Я столкнулся с той же проблемой,и решение, которое я нашел, состояло в реализации дополнительных черт.

Вы можете посмотреть на мой пост здесь.