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?
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?
Share Improve this question edited Feb 6 at 9:11 Some programmer dude 409k36 gold badges413 silver badges643 bronze badges asked Feb 6 at 8:58 DanielDaniel 3412 silver badges15 bronze badges2 Answers
Reset to default 4A 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
}
Well, I guess I'll end up answering my own question..
From https://en.cppreference.com/w/cpp/language/requires:
A simple requirement asserts that expression is valid. expression is an unevaluated operand.
Example given there:
template<typename T>
concept Addable = requires (T a, T b)
{
a + b; // "the expression “a + b” is a valid expression that will compile"
};
So the check passes because the expression is valid. The truthiness of the expression does not come into it because the expression is not evaluated. Only its syntactic validity is checked and since it is a legal expression, the requirement passes.