Функция шаблона, принимающая указатель на член, отказывается компилироваться

Рассмотрим этот класс:

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.)

«почему SFINAE не предотвращает это» - потому что вы не удалили функцию SFINAE в своем верхнем примере.

Ted Lyngmo 26.06.2024 21:43
минимальный воспроизводимый пример, пожалуйста, покажите, как вы создаете экземпляр шаблона. Мы здесь только догадываемся.
catnip 26.06.2024 21:44

@catnip, я отредактировал.

trbabb 26.06.2024 21:48

@TedLyngmo: Можно ли поконкретнее? Какие изменения необходимо внести в пример?

trbabb 26.06.2024 21:48
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) { ... } думаю сработает
Ted Lyngmo 26.06.2024 21:54

@trbabb SFINAE использует либо std::enable_if, либо C++20 concepts

Pepijn Kramer 26.06.2024 21:55

@Pepjin и @TedLyngmo: я рассмотрел это в своем вопросе. Использование оператора requires не решило проблему. @TedLyngmo, можно было бы подумать, что это сработает, но это не так.

trbabb 26.06.2024 21:57

@trbabb Компилирует для меня в g++, clang++, MSVC и icx. демо

Ted Lyngmo 26.06.2024 21:57

@TedLyngmo: Clang не для меня: replit.com/@tbabb/member-to-ptr-bug#main.cpp

trbabb 26.06.2024 22:00

@trbabb Это потому, что ты не сделал того, что сделал я. Примечание: Field U::* member_ptr не Field T::* member_ptr

Ted Lyngmo 26.06.2024 22:01

@TedLyngmo: Ах. Можете ли вы объяснить, почему это работает, а утверждение requires — нет?

trbabb 26.06.2024 22:02

Думаю, вам придется проделать тот же трюк с requires. Я не очень привык к концепциям, но, по крайней мере, для меня это работает.

Ted Lyngmo 26.06.2024 22:03

Хорошо. Ну, это решает проблему. Спасибо!

trbabb 26.06.2024 22:04

@trbabb - в вашем коде нет вывода типа, как может SFINAE потерпеть неудачу? вы говорите компилятору выполнить создание экземпляра, и это завершается с серьезной ошибкой. Можете использовать понятия, это то же самое: godbolt.org/z/h7Pn5WP4x

Gene 27.06.2024 01:28

@trbabb — еще одна вещь, которую вы можете сделать, — это предоставить частичную специализацию для неклассового типа, например: godbolt.org/z/316sqcaeq

Gene 27.06.2024 01:34

SFINAE применяется только в непосредственном контексте. Видите, дурак Что такое «непосредственный контекст», упомянутый в стандарте C++11, к которому применяется SFINAE?.

user12002570 27.06.2024 15:15
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
16
107
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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;
    }  
};

Спасибо за это! Я понял проблему, но не учел показанный здесь косвенный хак.

Drew Dormann 27.06.2024 15:31

Это проясняет; Спасибо. В моей ментальной модели любой объект шаблона, включая функцию-член шаблона, не определен/не существует до тех пор, пока не будет создан его экземпляр, и только тогда происходит замена. Похоже, существуют «стадии» замены, и это тонкий момент, о котором я не знал!

trbabb 29.06.2024 20:31

Другие вопросы по теме