Consider this class:
template <typename T>
struct Buffer {
template <typename Field>
int add_member_attribute(
std::string&& name,
Field T::* member_ptr)
{
// ...
return 0;
}
};
struct Thing {
int x;
double y;
};
int main() {
Buffer<Thing> bt;
Buffer<float> bf;
bt.add_member_attribute("y", &Thing::y);
}
If T
is a non-class type, then the compiler complains:
buffer.h:313:22: error: member pointer refers into non-class type 'float'
Field T::* member_ptr,
^
fixture.h:78:22: note: in instantiation of template class 'Buffer<float>' requested here
return f_real->items.data() + 32 * i;
^
This is unexpected to me because no code is even trying to invoke the function when T
is non-class. It seems like the error is being generated too early; why doesn't SFINAE prevent this?
Protecting add_member_attribute
with a requires std::is_class_v<T>
does not avoid the problem.
I've tried writing a helper class with a template specialization, including with deduction guides, in the hopes of using SFINAE to prevent the compiler from "seeing" the offending type expression:
template <typename Class, typename Field=void>
struct MemberPointer {
template <typename U>
MemberPointer(const U&) {} // no op
};
template <typename Class, typename Field>
requires std::is_class_v<Class>
struct MemberPointer<Class, Field> {
using type = Field Class::*;
type value;
MemberPointer(type value): value(value) {}
operator type() const {
return value;
}
};
// deduction guides
template <typename Class, typename Field>
MemberPointer(Field Class::* v) -> MemberPointer<Class, Field>;
template <typename T>
MemberPointer(T t) -> MemberPointer<T, void>;
template <typename T>
struct Buffer {
template <typename Field>
int add_member_attribute(
std::string&& name,
MemberPointer<T,Field> member_ptr)
{
// ...
}
};
Instead this prevents existing functions from matching the type:
cards.h:70:16: error: no matching member function for call to 'add_member_attribute'
cards->add_member_attribute(PrimitiveVariable::BOUND, &Card::extents);
~~~~~~~^~~~~~~~~~~~~~~~~~~~
buffer.h:311:19: note: candidate template ignored: could not match 'MemberType<Card, Field>' against 'range2 behold::Card::*'
AttributeInfo add_member_attribute(
How do I declare this function so that it is permitted for T
to be a non-class type?
(I'm using Apple Clang 15.0.)
SFINAE applies to "direct" context, here
template <typename T>
struct Buffer {
template <typename Field>
int add_member_attribute(
std::string&& name,
Field T::* member_ptr)
{
// ...
return 0;
}
};
T
is already fixed, and so
template <typename Field>
int add_member_attribute(
std::string&& name,
Field float::* member_ptr) // WRONG
{
// ...
return 0;
}
is wrong for any Field
.
As often, extra indirection helps:
template <typename T>
struct Buffer {
template <typename Field,
typename U = T, // To allow SFINAE
std::enable_if_t<std::is_same_v<T, U>, bool> = false> // Enforce that U == T
int add_member_attribute(
std::string&& name,
Field U::* member_ptr)
{
// ...
return 0;
}
};