I'm trying to write a requirement to check that a struct has a member with a given name and that it comes first in the struct. The address of the first member of a struct (At least standard layout) should have the same address as the encompassing struct, with no padding at the beginning. But since pointers are not normally compile-time constants, I would expect this to not compile. But it all compiles although the requirement then incorrectly passes.
template<typename T>
concept has_first_member = requires(T &s) {
(static_cast<void *>(&s) == static_cast<void *>(&s.next));
};
So given
struct mystruct{
int first;
int second;
int next;
};
the requirement would incorrectly accept this as valid.
Can anyone explain the behavior?
A solution that does not instantiate a variable of the type you're checking uses offsetof
:
template <typename T>
concept is_next_first = offsetof(T, next) == 0;
struct next_is_first {
int next;
int previous;
int something;
};
struct next_is_middle {
int previous;
int next;
int something;
};
struct next_is_last {
int previous;
int something;
int next;
};
static_assert(is_next_first<next_is_first>);
static_assert(not is_next_first<next_is_middle>);
static_assert(not is_next_first<next_is_last>);
Below is my first answer
You could use a constexpr
function to enable compile-time checks on a temporary variable.
struct next_is_first {
int next;
int previous;
int something;
};
struct next_is_middle {
int previous;
int next;
int something;
};
struct next_is_last{
int previous;
int something;
int next;
};
template <typename T>
constexpr bool check_next_is_first(const T& t) noexcept {
return (void *)&t == &t.next;
}
template <typename T>
concept is_next_first = check_next_is_first(T{});
static_assert( is_next_first<next_is_first> );
static_assert( not is_next_first<next_is_middle> );
static_assert( not is_next_first<next_is_last> );
Of course, this requires allocating and initializing the memory that an instance of the type you pass to is_next_first
needs, and that may be costly.
The concept is_next_first
can be used at a function
template <typename T>
requires is_next_first<T>
void do_something() { }
int main() {
do_something<next_is_first>();
do_something<next_is_middle>(); // does not compile
do_something<next_is_last>(); // does not compile
}