I'm using templates to explicitly declare and allow read access to specific data.
#include <type_traits>
template <typename T>
struct Access
{
template <typename U>
void Read()
{
static_assert(std::is_same_v<T, U>);
}
};
Normally T
would be a set of types, but I've simplified it here.
I'd like to ensure that any access that gets declared actually gets used. If a user declares Access<int>
I want to check to see that there is a corresponding Read<int>
somewhere.
Give that context, what I'm currently trying to do is detect whether Access<int>::Read<int>
ever gets instantiated. Is this possible?
I tried using extern template
to prevent implicit instantiations. The main problem here is that you have to explicitly write out every possible type at namespace scope. I don't see a way to do this systemically.
extern template void Access<int>::Read<int>();
int main()
{
auto IsIntReadUsed = &Access<int>::Read<int>; // Linker error, yay!
return 0;
}
Being able to detect this at compile time, link time, or run time is acceptable. The solution does not need to be portable across compilers. A solution that works on any single compiler is sufficient. Any C version is acceptable.
Here is a sandbox for experimenting https://godbolt.org/z/d5cco989v
// -----------------------------------------------------------
// Infrastructure
#include <type_traits>
template <typename T>
struct Access
{
template <typename U>
void Read()
{
static_assert(std::is_same_v<T, U>);
}
};
int main()
{
return 0;
}
// -----------------------------------------------------------
// User Code
using UserAccess = Access<int>;
void UserUpdate(UserAccess access)
{
// Oh no, access.Read<int> is never used!
(void) access;
}
// -----------------------------------------------------------
// Validation
template <typename TDeclared>
void ValidateAccess(Access<TDeclared>)
{
// Write something here that can tell if Access<TDeclared>::Read<TDeclared> has been
// used when this is called with UserAccess (i.e. ValidateAccess(UserAccess())).
// This could also be implemented inside the Access class.
}
CodePudding user response:
Here's a link-time solution. Works on GCC, Clang, and MSVC.
One template (impl::Checker<T>
) declares a friend function and calls it.
Another template (impl::Marker
) defines that function. If it's not defined, the first class gets an undefined reference.
#include <cstddef>
#include <type_traits>
namespace impl
{
template <typename T>
struct Checker
{
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-template-friend"
#endif
friend void adl_MarkerFunc(Checker<T>);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
static std::nullptr_t Check()
{
adl_MarkerFunc(Checker<T>{});
return nullptr;
}
inline static const std::nullptr_t check_var = Check();
static constexpr std::integral_constant<decltype(&check_var), &check_var> use_check_var{};
};
template <typename T>
struct Marker
{
friend void adl_MarkerFunc(Checker<T>) {}
};
}
template <typename T, impl::Checker<T> = impl::Checker<T>{}>
struct Access
{
template <typename U>
void Read()
{
static_assert(std::is_same_v<T, U>);
(void)impl::Marker<U>{};
}
};
int main()
{
Access<int> x;
x.Read<int>();
[[maybe_unused]] Access<float> y; // undefined reference to `impl::adl_MarkerFunc(impl::Checker<float>)'
using T [[maybe_unused]] = Access<double>; // undefined reference to `impl::adl_MarkerFunc(impl::Checker<double>)'
}
Had to introduce a dummy template parameter to Access
, since I couldn't think of any other way of detecting it being used in a using
.