Почему спецификатор noexcept не включен в объявленный метод?

Пытаясь разработать некоторые классы без исключений, у меня есть структура наследования, подобная этой, но я обнаружил, что спецификатор noexcept практически не помогает при работе с функциями-членами, поскольку спецификатор не ограничен областью действия. как внутри функции.

class Base
{
protected:
    Base() noexcept {}
};

class Derived : public Base
{
public:
    // error: 'Base::Base()' is protected
    Derived() noexcept(noexcept(Base{})) : Base{} {}

    // error: 'foo' was not declared in this scope
    Derived(int) noexcept(noexcept(foo())) {}

    // error: invalid use of 'this' at top level
    Derived(float) noexcept(noexcept(this->foo())) {}

    void foo() noexcept {}
};

Демо

Возможно, это то, что улучшается в С++ 17? Попытка найти это не дала подходящих результатов. На данный момент я смирился с некоторыми очень уродливыми (и, возможно, неправильными) попытками, такими как noexcept(noexcept(static_cast<Derived*>(nullptr)->foo())), но это не помогает в случае защищенного конструктора базового класса.

Возможно ли в настоящее время объявить спецификатор noexcept, который ссылается на защищенный метод базового класса? noexcept(auto) может иметь значение, но конечно пока нельзя. Я упустил из виду что-то еще, что позволило бы мне включить этот спецификатор, или в этом случае я просто должен его опустить?


person monkey0506    schedule 07.03.2016    source источник


Ответы (2)


Вы можете обойти это, используя выражение, в котором конструктор Base находится в области видимости следующим образом:

struct test_base : public Base {};

Derived() noexcept(noexcept(test_base())) : Base() {}

Я считаю, что причина, по которой вы не можете использовать Base() напрямую, связана с этим вопросом.

То, как работает спецификатор защищенного доступа, позволяет производному классу B получать доступ к содержимому объекта базового класса A только тогда, когда этот объект класса A является подобъектом класса B. Это означает, что единственное, что вы можете сделать в своем коде заключается в доступе к содержимому A через B: вы можете получить доступ к членам A через указатель типа B * (или ссылку типа B &). Но вы не можете получить доступ к одним и тем же членам через указатель типа A * (или ссылку A &).

Это то же самое, как если бы у вас была такая функция-член:

void badfunc()
{
    B b;
}

Вы пытаетесь использовать конструктор Base напрямую, а не сквозь Derived. Когда вы инициализируете базу в списке инициализации конструктора, это особый контекст, который позволяет вам вызывать конструктор, потому что вы делаете это как часть инициализации Derived.

person Kurt Stutsman    schedule 07.03.2016
comment
Я забыл упомянуть, что в противном случае этот код правильно компилируется в clang. Другие случаи могут быть ошибкой в ​​​​gcc, но я все еще изучаю стандарт. - person Kurt Stutsman; 08.03.2016
comment
Я обновил свой ответ, чтобы, надеюсь, пролить свет на недоступность Base(). - person Kurt Stutsman; 08.03.2016

Это действительно несколько вопросов в одном.

О this...

Насколько я понимаю, использование this предполагается совершенно излишним, но поддержка компилятором C++11 не совсем универсальна. Это должно работать в соответствии со стандартом С++ 11:

struct Base {
    void func() noexcept;
};
struct Derived() {
    void func() noexcept(noexcept(Base::func())) {}
};

Обратите внимание, что base_func() — это нестатическая функция-член, но поскольку она появляется в невычисленном операнде, с ней все в порядке. Из n3337 сек 4.1.1:

id-expression, обозначающий нестатический член данных или нестатическую функцию-член класса, можно использовать только:

...

  • если это id-expression обозначает нестатический элемент данных и появляется в невычисленном операнде.

Однако некоторые компиляторы этого не поддерживают. Затем вы вынуждены использовать std::declval:

#include <utility>
struct Base {
    void func() noexcept;
};
struct Derived() {
    void func() noexcept(noexcept(std::declval<Base>().func())) {}
};

О доступности...

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

int f(); // No definition anywhere in program
int x = sizeof(f()); // Completely valid, even without definition of f

struct X {
    X() = delete; // Cannot use this constructor
};
int xsize = sizeof(X{}); // Completely valid

Несмотря на то, что это несколько неясно, мне трудно представить, что комитет C++ мог разрешить вам использовать удаленные функции-члены в невычисленных операндах, но не недоступные функции-члены. Однако я не уверен.

Обратите внимание, что приведенный выше код компилируется без ошибок как с GCC, так и с Clang. Однако следующий код не работает:

class X {
    X(){}
};
class Y {
    Y() = delete;
};
bool xokay = noexcept(X{}); // Error!
bool yokay = noexcept(Y{}); // Ok

И GCC, и Clang принимают Y, но не X, что выглядит немного странно, если не сказать больше. Следующий код принимается Clang, но не GCC, и использование std::declval не помогает:

class Base {
protected:
    void func();
};
class Derived : public Base {
    // Clang accepts this, GCC does not.
    void func2() noexcept(noexcept(Base::func())) {}
};

Какой беспорядок.

Выводы

Вывод здесь состоит в том, что кажется, что есть много несоответствий, которые нужно обойти, и много пробелов между текущими компиляторами и спецификациями C++11.

person Dietrich Epp    schedule 08.03.2016
comment
Я почти уверен, что вы хотели сказать std::declval вместо std::decltype. - person Barrett Adair; 13.06.2016