Подтвердить что ты не робот

Как явным образом вызывать метод исключения исключений в С++?

У меня есть простой класс:

class A {
 public:
  bool f(int* status = nullptr) noexcept {
    if (status) *status = 1;
    return true;
  }
  void f() {
    throw std::make_pair<int, bool>(1, true);
  }
};

int main() {
  A a;
  a.f(); // <- Ambiguity is here! I want to call 'void f()'
}

Я хочу разрешить неоднозначность вызова метода в пользу метода throw-throw любым способом.

Обоснование такого интерфейса:

  • Чтобы иметь интерфейс noexcept(true) и noexcept(false),
  • Чтобы дополнительно получить дополнительную информацию с помощью указателя в варианте noexcept(false), в то время как вариант noexcept(true) всегда будет упаковывать эту информацию в исключение.

Возможно ли вообще? Предложения по улучшению интерфейса также приветствуются.

4b9b3361

Ответ 1

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

Однако, если вы застряли с интерфейсом, который вы не можете изменить или просто для удовольствия, вот как вы можете явно вызвать void f():

Фокус в том, чтобы использовать кастинг для указателя функции для устранения двусмысленности:

a.f(); // <- ambiguity is here! I want to call 'void f()'

(a.*(static_cast<void (A::*)()>(&A::f)))(); // yep... that the syntax... yeah...

Хорошо, поэтому он работает, но никогда не пишут такой код!

Есть способы сделать его более читаемым.

Используйте указатель:

// create a method pointer:
auto f_void = static_cast<void (A::*)()>(&A::f);

// the call is much much better, but still not as simple as `a.f()`
(a.*f_void)();

Создайте лямбда или свободную функцию

auto f_void = [] (A& a)
{
    auto f_void = static_cast<void (A::*)()>(&A::f);
    (a.*f_void)();
};

// or

void f_void(A& a)
{
    auto f_void = static_cast<void (A::*)()>(&A::f);
    (a.*f_void)();
};


f_void(a);

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

Ответ 2

Обе версии f имеют разные значения.

Они должны иметь два разных имени:

  • f для броска, потому что использование этого означает, что вы уверены в успехе, а сбой будет исключением в программе.
  • try_f() или tryF() для основанного на ошибке возврата, поскольку использование этого означает, что отказ вызова является ожидаемым результатом.

Два разных значения должны отражаться в дизайне с двумя разными названиями.

Ответ 3

Поскольку это кажется мне принципиально очевидным, я, возможно, что-то упускаю или не могу полностью понять ваш вопрос. Однако, я думаю, это делает именно то, что вы хотите:

#include <utility>

class A {
 public:
  bool f(int* status) noexcept {
    if (status) *status = 1;
    return true;
  }
  void f() {
    throw std::make_pair<int, bool>(1, true);
  }
};

int main() {
  A a;
  a.f(); // <- now calls 'void f()'
  a.f(nullptr);  // calls 'bool f(int *)'
}

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

Ответ 4

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

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

См. например std::filesystem::file_size, который имеет две перегрузки, одна из которых noexcept:

std::uintmax_t file_size( const std::filesystem::path& p );

std::uintmax_t file_size( const std::filesystem::path& p,
                          std::error_code& ec ) noexcept;

Идея этого дизайна (первоначально из Boost.Filesystem) почти идентична вашей, за исключением аргумента по умолчанию. Удалите его, и вы сделаете это как новый компонент стандартной библиотеки, который, очевидно, можно ожидать, что он не будет иметь полностью сломанный дизайн.

Ответ 5

В С++ 14 это неоднозначно, потому что noexcept не является частью сигнатуры функции. С этим сказал...

У вас очень странный интерфейс. Хотя f(int* status = nullptr) помечен noexcept, потому что у него есть близнец, который генерирует исключение, вы на самом деле не предоставляете вызывающей стороне логическую гарантию исключения. Кажется, вы одновременно хотите, чтобы f всегда преуспевал, бросая исключение, если предварительное условие не выполняется (статус имеет допустимое значение, т.е. nullptr). Но если f выбрасывает, в каком состоянии находится объект? Видите ли, ваш код очень трудно понять.

Я рекомендую вам взглянуть на std::optional. Это даст понять читателю, что вы на самом деле пытаетесь сделать.

Ответ 6

У С++ уже есть тип, специально используемый в качестве аргумента для устранения неоднозначности между металическими и небрасывающими вариантами функции: std::nothrow_t. Вы можете использовать это.

#include <new>

class A {
 public:
  bool f(std::nothrow_t, int* status = nullptr) noexcept {
    if (status) *status = 1;
    return true;
  }
  void f() {
    throw std::make_pair<int, bool>(1, true);
  }
};

int main() {
  A a;
  a.f(); // Calls 'void f()'
  a.f(std::nothrow); // Calls 'void f(std::nothrow_t, int*)'
}

Хотя я бы предпочел интерфейс, в котором имя отличает варианты, или, возможно, одно, где различие не требуется.

Ответ 7

Здесь чисто метод времени компиляции.

Это может быть полезно, если у вашего компилятора возникли проблемы с оптимизацией вызовов указателей функций.

#include <utility>

class A {
 public:
  bool f(int* status = nullptr) noexcept {
    if (status) *status = 1;
    return true;
  }
  void f() {
    throw std::make_pair<int, bool>(1, true);
  }
};

template<void (A::*F)()>
struct NullaryFunction {
  static void invoke(A &obj) {
    return (obj.*F)();
  }
};

int main() {
  A a;
  // a.f(); // <- Ambiguity is here! I want to call 'void f()'
  NullaryFunction<&A::f>::invoke(a);
}

Ответ 8

Итак, вы пытаетесь выдать исключение, если код не готов для возврата ошибки?

Затем, как насчет

class ret
{
    bool success;
    mutable bool checked;
    int code;
public:
    ret(bool success, int code) : success(success), checked(false), code(code) { }
    ~ret() { if(!checked) if(!success) throw code; }

    operator void *() const { checked = true; return reinterpret_cast<void *>(success); }
    bool operator!() const { checked = true; return !success; }

    int code() const { return code; }
};

Это все равно Отвращение к Нуггану.

Удалив проверку if(!success) в деструкторе, вы можете сделать код броском всякий раз, когда код возврата не просматривается.