Рассмотрим этот класс:
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);
}
Если T
не является типом класса, компилятор жалуется:
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;
^
Для меня это неожиданно, потому что ни один код даже не пытается вызвать функцию, если T
не является классом. Кажется, ошибка генерируется слишком рано; почему SFINAE не предотвращает это?
Защита add_member_attribute
с помощью requires std::is_class_v<T>
не позволяет избежать проблемы.
Я попытался написать вспомогательный класс со специализацией шаблона, в том числе с руководствами по выводам, в надежде использовать SFINAE, чтобы не дать компилятору «увидеть» выражение оскорбительного типа:
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)
{
// ...
}
};
Вместо этого это не позволяет существующим функциям соответствовать типу:
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(
Как мне объявить эту функцию, чтобы T
мог быть типом, не относящимся к классу?
(Я использую Apple Clang 15.0.)
@catnip, я отредактировал.
@TedLyngmo: Можно ли поконкретнее? Какие изменения необходимо внести в пример?
template <class Field, class U = T>
std::enable_if_t<std::is_class_v<U>, AttributeInfo>
add_member_attribute(AttributeKey&& name, Field U::* member_ptr) { ... }
думаю сработает
@trbabb SFINAE использует либо std::enable_if
, либо C++20 concepts
@Pepjin и @TedLyngmo: я рассмотрел это в своем вопросе. Использование оператора requires
не решило проблему. @TedLyngmo, можно было бы подумать, что это сработает, но это не так.
@trbabb Компилирует для меня в g++, clang++, MSVC и icx. демо
@TedLyngmo: Clang не для меня: replit.com/@tbabb/member-to-ptr-bug#main.cpp
@trbabb Это потому, что ты не сделал того, что сделал я. Примечание: Field U::* member_ptr
не Field T::* member_ptr
@TedLyngmo: Ах. Можете ли вы объяснить, почему это работает, а утверждение requires
— нет?
Думаю, вам придется проделать тот же трюк с requires
. Я не очень привык к концепциям, но, по крайней мере, для меня это работает.
Хорошо. Ну, это решает проблему. Спасибо!
@trbabb - в вашем коде нет вывода типа, как может SFINAE потерпеть неудачу? вы говорите компилятору выполнить создание экземпляра, и это завершается с серьезной ошибкой. Можете использовать понятия, это то же самое: godbolt.org/z/h7Pn5WP4x
@trbabb — еще одна вещь, которую вы можете сделать, — это предоставить частичную специализацию для неклассового типа, например: godbolt.org/z/316sqcaeq
SFINAE применяется только в непосредственном контексте. Видите, дурак Что такое «непосредственный контекст», упомянутый в стандарте C++11, к которому применяется SFINAE?.
SFINAE применяется к «прямому» контексту, здесь
template <typename T>
struct Buffer {
template <typename Field>
int add_member_attribute(
std::string&& name,
Field T::* member_ptr)
{
// ...
return 0;
}
};
T
уже исправлено, и так
template <typename Field>
int add_member_attribute(
std::string&& name,
Field float::* member_ptr) // WRONG
{
// ...
return 0;
}
неправильно для любого Field
.
Как часто, дополнительная косвенность помогает:
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;
}
};
Спасибо за это! Я понял проблему, но не учел показанный здесь косвенный хак.
Это проясняет; Спасибо. В моей ментальной модели любой объект шаблона, включая функцию-член шаблона, не определен/не существует до тех пор, пока не будет создан его экземпляр, и только тогда происходит замена. Похоже, существуют «стадии» замены, и это тонкий момент, о котором я не знал!
«почему SFINAE не предотвращает это» - потому что вы не удалили функцию SFINAE в своем верхнем примере.