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