Разве не имеет смысла перегрузить noexcept?

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

Это нерабочий пример этой ситуации,

void f(){}
void f() noexcept{} // not allowed in c++

void g(){f();} // should call f
void h() noexcept{f();} // should call f noexcept
int main(){
    g();
    h();
}

Если в вызывающей функции (h) нет блока try/catch, то компилятор может сделать вывод, что кто-то заинтересован в вызове конкретной функции f.

Используется ли этот шаблон в каком-либо другом обходном пути?

Все, что я могу себе представить, это что-то вроде этого, но это не очень общее:

template<bool NE> void F() noexcept(NE);

template<>
void F<true>() noexcept(true){}
template<>
void F<false>() noexcept(false){}

void g(){F<noexcept(g)>();} // calls F<false>
void h() noexcept{F<noexcept(h)>();} // call F<true>

Некоторые могут задаться вопросом, почему это имеет смысл. Моя логика такова, что С++ позволяет перегружать по отношению к const как аргумент функций, так и функции-члены. Например, const функций-членов предпочитают вызывать const перегрузок членов.

Я думаю, что для noexcept функций имеет смысл вызывать noexcept "перегрузки". Особенно, если они не вызываются из блока try/catch.


person alfC    schedule 18.01.2018    source источник
comment
С С++ 17 вы можете использовать if constexpr как в: template<bool NE> void F() noexcept(NE) { if constexpr(NE) { ... } else { ... } }. Не уверен, что это полезно для вашего вопроса.   -  person Innocent Bystander    schedule 18.01.2018
comment
Каков вариант использования такой перегрузки? Либо то, что делает f, не может дать сбой, и в этом случае нет необходимости в версии noexcept(false), либо он может дать сбой, и поэтому версии noexcept(true) нужен какой-то способ сообщить об этом сбое, что обычно означает другую сигнатуру.   -  person T.C.    schedule 18.01.2018
comment
Если в вызывающей функции (h) нет блока try/catch, то компилятор может сделать вывод, что кто-то заинтересован в вызове конкретной функции f. Большинство защищенных от исключений кодов не имеют блока try; он опирается на RAII. Он все равно захочет вызвать версию noexcept(false).   -  person Todd Fleming    schedule 19.01.2018
comment
Если вы хотите, чтобы в версиях одна ошибка сообщалась по возвращаемому значению, а другая — по исключению, взгляните на std::nothrow и на то, как она используется.   -  person Deduplicator    schedule 19.01.2018
comment
@Дедупликатор, отлично. Не знал о std::nothrow, кажется, это идиома, которой нужно следовать.   -  person alfC    schedule 19.01.2018
comment
@TC, начнем с того, что реализации могут быть разными. noexcept не будет означать, что он не может потерпеть неудачу, это будет означать, что он может потерпеть неудачу, но не из-за исключений (а с прерыванием). Я думаю, что пример в cppreference для new в комментарии Deduplicator очень показателен.   -  person alfC    schedule 19.01.2018
comment
@InnocentBystander интересно, но NE не будет выводиться, верно?   -  person alfC    schedule 20.01.2018
comment
@alfC, верно.   -  person Innocent Bystander    schedule 21.01.2018
comment
@alfC, кстати, библиотека asio использует аналогичный подход к std::nothrow. Имеет 2 перегрузки для многих функций, одна из которых noexcept и возвращает код ошибки; другой -- выдает исключение.   -  person Innocent Bystander    schedule 21.01.2018
comment
@InnocentBystander спасибо за информацию. Интересно, если кто-то в конечном итоге вызовет функции nothrow из функций nothrow (и без включения try catch), поэтому было бы неплохо автоматически выбрать правильный.   -  person alfC    schedule 21.01.2018


Ответы (2)


Я не думаю, что перегрузка noexcept имеет смысл сама по себе. Конечно, имеет смысл, если ваша функция f не является исключением, особенно при вызове из h, так как h необходимо поймать возможное исключение и вызвать std::abort.

Однако просто перегружать noexcept не очень хорошо. Это как отключить исключения в стандартной библиотеке. Я не утверждаю, что вы не должны этого делать, но из-за этого вы теряете функциональность. Например: std::vector::at выдает, если индекс недействителен. Если вы отключите исключения, у вас не будет альтернативы использованию этой функции.

Поэтому, если вы действительно хотите иметь 2 версии, вы можете использовать другие альтернативы для обозначения отказа. std::необязательный, std::expected, std::error_code...

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

Следовательно, я думаю, что лучше перегружать по-другому, чтобы пользователь мог выбрать, какой вариант использовать, явно используя логическое значение, std::nothrow в качестве выходного аргумента аргумента с std::error_code. Или, может быть, вам следует выбрать стратегию обработки ошибок, которую вы используете в своей библиотеке, и применить ее к своим пользователям.

person JVApen    schedule 27.06.2020

Это имеет смысл,

Конечно, это имело бы смысл в принципе. Одна версия функции может запускать, скажем, более быстрый алгоритм, но для этого требуется динамически выделяемая дополнительная рабочая память, в то время как версия noexcept может использовать более медленный алгоритм с O(1) дополнительного пространства в стеке.

но не смог бы решить перегрузку...

Как вы, возможно, знаете, вполне допустимо вызывать noexcept(false) функции из noexcept(true) функций. Вы просто рискуете получить terminate вместо исключения; а иногда - вы ничем не рискуете, потому что вы убедились, что входные данные, которые вы передаете, не могут вызвать исключение. Так как же компилятор узнает, какую версию функции вы вызываете? И тот же вопрос в другом направлении - может быть, вы хотите вызвать свою функцию noexcept(true) из функции noexcept(false)? Это тоже разрешено.

... и - в любом случае это будет в основном синтаксический сахар

С С++ 11 вы можете написать:

#include <stdexcept>

template <bool ne2>
int bar(int x) noexcept(ne2);

template<> int bar<true>(int) noexcept { return 0; }
template<> int bar<false>(int) { throw std::logic_error("error!"); }

и это прекрасно компилируется: GodBolt.

Таким образом, у вас может быть две функции с одинаковыми и одинаковыми аргументами, отличающиеся только w.r.t. их значение noexcept, но с разными аргументами шаблона.

person einpoklum    schedule 08.06.2020