0

I'm using a library that I can't edit and don't want to fork, which has a bunch of functions of the int error return style:

int libnamespace_dosomething(int, sometype*);

I'd like the library to have [[nodiscard]] or __attribute__((warn_unused_result)) on most, probably all of the interface, so that the compiler will bark when I fail to check whether a call succeeded.

Any suggestions for how to achieve this?

Current ideas are quite labour intensive:

  • editing the header, fixing errors, putting the header back. Repeat occasionally
  • something ugly based on macros, might be able to generate the intercept from nm | bash
  • clang plugin

edit: wrapper approach I was considering is

int libcall(float);
#define libcall(...) mylibcall(__VA_ARGS__)
[[nodiscard]] inline int mylibcall(float x)
{
  return (libcall)(x);
}
Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
Jon Chesterfield
  • 2,251
  • 1
  • 20
  • 30
  • 1
    It seems like you want to change the signature of the functions in that library *without* actually modifying that library at all. That doesn't seem like something that would be possible. – cigien May 20 '21 at 14:33
  • 3
    If you want to improve their API by providing a better API, it sounds like wrapper time. – Wyck May 20 '21 at 14:35
  • 1
    If the library is open source you could also create a pull request where you've made the functions `[[nodiscard]]` - if it's accepted, everybody wins :) – Ted Lyngmo May 20 '21 at 14:38
  • @cigien Are you saying adding the attribute breaks ABI? I'm not saying you're wrong, but that sounds rather insane to me, since this attribute is a caller side compiler-only flag. That said, yeah, a custom parser to generate inline wrappers that just forward the call for each function is probably the safest route, and it's not *that* complicated, especially since the attribute is at the beginning of the declaration, so you don't have to parse much -- basically just a type, a name and a parenthesis. – Blindy May 20 '21 at 14:45
  • @Blindy No, I'm just saying that adding attributes to the library functions, when the OP *can't edit* it seems impossible. I might have misunderstood the OP's constraints though. – cigien May 20 '21 at 14:47
  • @cigien He can always edit the header file, as header files are source code for both library and caller, he most likely means he can't edit the library binary itself though. So as long as ABI is preserved (and it seems like it should be), that should work! – Blindy May 20 '21 at 14:48
  • Adding wrapper functions *may* be suitable, depending on how many functions there are in the API that need wrapping. In `MyHeader.h`, stuff like `[[nodiscard]] int MyFoo() { return Foo(); }`. – Adrian Mole May 20 '21 at 14:54

1 Answers1

0

In hindsight, I should have tried the obvious before posting.

Clang treats the attribute as additive on repeat declarations,

[[nodiscard]] int libnamespace_dosomething(int, sometype*);

so writing that ^ before, or after, the declaration in the header is included works.

With some effort it's probably possible to wrap that in a macro to handle the repeated typing. I think

#define NODISCARD(ARITY,X) /*careful*/

is implementable in C++, such that

NODISCARD(2, libnamespace_dosomething);

expands to a declaration that matches the function passed but with nodiscard added. Not confident about inferring the arity from a function pointer. I'll update this answer if I hack out the above.

Example of the above suggestion

#include <tuple>

namespace nodiscard {
// Extract return / argument types from address of function symbol
template <typename F> struct trait;
template <typename R, typename... Ts> struct trait<R (*)(Ts...)> {
  constexpr static const size_t nargs = sizeof...(Ts);
  typedef R ReturnType;
  template <size_t i> struct arg {
    typedef typename std::tuple_element<i, std::tuple<Ts...>>::type type;
  };
};
} // namespace nodiscard

#define NODISCARD_INSTANTIATE(SYM, ARITY)                                      \
  static_assert(ARITY == nodiscard::trait<decltype(&SYM)>::nargs,              \
                "Arity Error");                                                \
  NODISCARD_INSTANTIATE_##ARITY(SYM, nodiscard::trait<decltype(&SYM)>)

#define NODISCARD_INSTANTIATE_0(SYM, T) [[nodiscard]] T::ReturnType SYM();

#define NODISCARD_INSTANTIATE_1(SYM, T)                                        \
  [[nodiscard]] T::ReturnType SYM(typename T::template arg<0>::type);

#define NODISCARD_INSTANTIATE_2(SYM, T)                                        \
  [[nodiscard]] T::ReturnType SYM(typename T::template arg<0>::type,           \
                                  typename T::template arg<1>::type);

#define NODISCARD_INSTANTIATE_3(SYM, T)                                        \
  [[nodiscard]] T::ReturnType SYM(typename T::template arg<0>::type,           \
                                  typename T::template arg<1>::type,           \
                                  typename T::template arg<2>::type);

// had to go up to 8, copy&paste style
Jon Chesterfield
  • 2,251
  • 1
  • 20
  • 30