Я имею дело с C-библиотекой, предоставляемой поставщиком (назовем ее foo
), которая предлагает множество вызовов API. Все эти функции принимают (предварительно инициализированный) дескриптор (типа FOOHandle
) и возвращают 0 в случае успеха и какое-то другое число в случае неудачи. В последнем случае вызывающий абонент может вызвать foo_get_errmsg()
, чтобы получить текстовое описание ошибки, сохраненное в его собственном буфере. Ничего особенного.
Программируя на C++, я хотел бы превратить эти ошибки в исключения моего типа. С этой целью я создал свой собственный класс FOOException
со следующим конструктором:
private:
char buf[1024];
int code;
public:
FOOException(FOOHandle h, int32_t _code) noexcept : code(_code)
{
foo_get_errmsg(code, h, buf);
}
Все идет нормально.
Чтобы помочь преобразовать существующий код, я хотел бы добавить общую функцию-оболочку, которая будет вызывать указанный вызов API, проверять его результат, а затем выдавать новое исключение, если это необходимо:
static auto wrapper(const auto &foofunc, auto&&... args) const
{
auto code = foofunc(args...);
switch (code) {
case 0:
return 0;
default:
throw FOOException(handle, code);
}
}
Моя проблема в том, чтобы получить это handle
... Хотя аргумент всегда присутствует во всех вызовах FOO-функций - поэтому всегда среди args...
- он не всегда первый:
foo_analyze(a, handle);
...
foo_meow(handle, a, b, c, d);
Есть ли какой-нибудь способ «выловить» дескриптор из args...
(по имени или по типу), чтобы его можно было использовать и в оболочке, или я должен дублировать его во всех вызовах wrapper()
— один раз для самой оболочки, и с другой стороны, для оборачиваемой функции?
Кажется, что проблемная область на самом деле шире, чем вопрос. Сначала я бы предоставил оболочку RAII для дескрипторов; обычно они могут быть только подвижными или неподвижными. Функции, возвращающие коды ошибок, обычно возвращают фактический результат в виде аргумента-указателя; если это так, я бы предоставил функции-оболочки, которые возвращают std::expected
или сторонний эквивалент (boost::outcome
) с int
в качестве типа ошибки. Традиционная обработка исключений — использование try+catch — больше не является канонической тенденцией. Монадические классы являются рекомендуемым решением.
Предполагая, что из FooHandle
есть только один Args
, вы можете сделать что-то вроде (создать std::tuple
и извлечь по типу):
auto& handle = std::get<FooHandle&>(std::tie(args...));
Отлично... И компилятору хватило бы ума понять это во время компиляции, не так ли?
Да, это время компиляции.
дескрипторы в C обычно являются int и void*, могут конфликтовать с другими параметрами
Хорошая мысль, @Gene. В этой библиотеке это void *
. Хуже того, это #define
-d как таковой, даже не typedef
. Откуда мне знать о конфликте — будет ли это ошибкой компилятора или он может просто молча выбрать другой указатель?
@МихаилТ. Если FOOHandle
не является уникальным типом (даже typedef
— это просто псевдоним, а не тип), то это значительно усложняет задачу, если не делает ее невозможной, если какая-либо из функций API использует void*
для других целей. Возможно, вам придется определить свой собственный тип, чтобы обернуть любой FOOHandle
, который вы передаете, в свой wrapper
, чтобы вы могли его идентифицировать.
Я думаю, что это единственный void *
, но мне бы хотелось, чтобы компилятор сказал мне, если я ошибаюсь. Будет ли это? Или он незаметно отберет первый void *
у args...
?
@МихаилТ. std::tie()
возвращает std::tuple
, тип которого может повторяться несколько раз. Однако std::get()
не удастся скомпилировать, если вы запросите повторяющийся тип.
Вы можете вручную распаковать параметры шаблона, пока не найдете дескриптор, например:
FOOHandle getfoohandle() {
return nullptr; // or whatever constitutes an "invalid" handle...
}
FOOHandle getfoohandle(FOOHandle h, auto&&...) {
return h;
}
FOOHandle getfoohandle(auto&&, auto&&... args) {
return getfoohandle(std::forward<decltype(args)>(args)...);
}
static void wrapper(const auto &foofunc, auto&&... args) const
{
auto code = foofunc(std::forward<decltype(args)>(args)...);
if (code != 0) {
FOOHandle handle = getfoohandle(std::forward<decltype(args)>(args)...);
throw FOOException(handle, code);
}
}
ОБНОВЛЕНИЕ: для компиляторов до C++20 auto
не разрешено в параметрах функции, поэтому вместо этого вам придется использовать template
параметры, например:
FOOHandle getfoohandle() {
return nullptr; // or whatever constitutes an "invalid" handle...
}
template<typename... Args>
FOOHandle getfoohandle(FOOHandle h, Args&&...) {
return h;
}
template<typename Param1, typename... Args>
FOOHandle getfoohandle(Param1&&, Args&&... args) {
return getfoohandle(std::forward<Args>(args)...);
}
template<typename Func, typename... Args>
static void wrapper(const Func &foofunc, Args&&... args) const
{
auto code = foofunc(std::forward<Args>(args)...);
if (code != 0) {
FOOHandle handle = getfoohandle(std::forward<Args>(args)...);
throw FOOException(handle, code);
}
}
не выбрасывайте указатель, выбрасывайте по значению
@Gene, это была копия/вставка исходного кода ОП. Я исправил это в своем примере, но вместо этого вам следует направить комментарий в ОП.
Эх, пытаюсь скомпилировать свою новую оболочку с std=c++17, получаю error: 'auto' not allowed in function prototype
. Использование C++20 мне подходит, но у моих коллег, использующих Windows, этого нет в компиляторе VС++ :(
@МихаилТ. auto
параметров еще не существовало в C++17. MSVC поддерживает C++20, начиная с VS 2019 v16.11 с переключателем /std:c++20
. Для компиляторов до C++20 вместо этого вам придется использовать параметры template
. Я обновил свой ответ, чтобы показать это.
У меня это работало для лямбда-функции раньше...
@МихаилТ. Однако лямбды не являются функциями. auto
для лямбда-параметров было добавлено в C++14, а auto
для параметров функции было добавлено в C++20.
Кажется, даже с С++ 17 работает следующее: static const auto wrapper = [](const auto &func, auto&&... args)
...
Как насчет пространства имен-оболочки, которое упорядочивает дескриптор, чтобы он всегда был первым аргументом. Введите
namespace std_foo
, а затемanalyze()
, то естьanalyze(handle,a)
, это немного усилий (хотя при встроенной компиляции не будет никаких накладных расходов), и давайте посмотрим правде в глаза API, где дескриптор, который является первым аргументом, немного эксцентричен, не так ли?