Почему указатели на элементы данных можно вызывать в C++?

На странице «Вызов» в cppreference указано:

Примечания. Указатели на элементы данных являются вызываемыми, даже если вызовы функций не выполняются.

Указатели на члены функций имеют смысл: почему указатели на члены данных являются вызываемыми, если их нельзя вызвать?

Указатели на участников очень полезны в качестве функций проекции. Предположим, у вас есть struct S { int i; int j; } и вы хотите найти элемент в std::vector<S> vec по i = 1. Вы можете сделать: std::ranges::find(vec, 1, &S::i);. Никаких лямд.

Evg 12.06.2024 03:00

Я бы сказал, что «вызываемый» может быть более подходящим именем, потому что оно напрямую относится к INVOKE, но в любом случае «вызываемый» — это то, что сейчас есть в стандарте.

Weijun Zhou 12.06.2024 06:15
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
27
2
2 241
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Стандарт определяет это так. [func.def]/3

Вызываемый тип — это тип функционального объекта ([function.objects]) или указатель на член.

Стандарты определяют INVOKE для указателей на члены данных. func.require

  1. Определите INVOKE(f, t1 , t2 , …, tN ) следующим образом:

    (1.4) - t1.*f когда N = 1 и f являются указателями на член данных класса T и is_same_v<T, remove_cvref_t<decltype(t1)>> || is_base_of_v<T, remove_cvref_t<decltype(t1 )>> истинно;

    (1.5) - t1.get().*f когда N = 1 и f являются указателями на член данных класса T и remove_cvref_t<decltype(t1 )> является специализацией reference_wrapper;

    (1.6) - (*t1 ).*f когда N = 1 и f являются указателями на член данных класса T и t1 не удовлетворяет двум предыдущим пунктам;

Грубо говоря, результат вызова элемента данных — это значение элемента данных.

Указатели на члены данных можно «вызывать» в том смысле, что их можно вызывать, даже если фактического вызова функции не происходит. См. Функциональные объекты ‒ указатель на элемент данных можно «вызвать», извлекая член целевого объекта, что также делает std::invoke, когда ему дан указатель на член.

Почему, я предполагаю удобство. Полезно иметь возможность обрабатывать указатель на элемент как функцию, которая просто извлекает элемент, поскольку с такой вещью, как правило, больше ничего нельзя сделать, как и с указателями на функции.

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

Указатели на члены данных являются вызываемыми типами по определению.

Обратите внимание, что это не относится к использованию оператора вызова (), поскольку к указателям на члены данных и указателям на функции-члены не может быть применен оператор (); однако их можно вызвать. Выражение только для экспозиции INVOKE (доступное через std::invoke) работает с любым вызываемым типом.

Для указателя элемента данных fINVOKE(f, t) расширяется до t.*f ([func.require] p1.4). Таким образом, указатели на члены данных действуют как функция, возвращающая член объекта.

Указатели на вызываемые элементы данных полезны.

Они могут быть очень полезны в качестве проекций в новых алгоритмах C++20:

struct S { int x; };
std::vector<S> range = /* ... */;

// sort range using std::ranges::less and using &S::x as a projection
std::ranges::sort(range, {}, &S::x);

Такой вызов std::ranges::sort будет сортировать range путем сравнения члена x каждого объекта S. Это работает, поскольку алгоритмы принимают любой вызываемый тип, а не только типы с оператором вызова.

Примечание о развитии речи

Согласитесь, терминология очень запутанная. Оглядываясь назад, вместо этого «вызываемые типы» следовало бы назвать «вызываемыми типами».

Также были предложения добавить оператор () к указателям на члены; совсем недавно появился P1214: указатель на функции-члены и объекты-члены — это просто вызываемые объекты!.

Это похоже на Python property, где доступ к нему через экземпляр действительно вызывает метод property__get__ с экземпляром в качестве аргумента.

chepner 12.06.2024 17:50

@chepner Note that this doesn't refer to the use of the call operator () это определение из библиотечного API, а не из синтаксиса языка (где указатели на члены представляют собой отдельный класс типов, отличный от указателей на функции или других указателей). По сути, это говорит о том, что функции, которые могут принимать вызываемый объект, должны иметь перегрузку для указателя на член.

Swift - Friday Pie 15.06.2024 10:35

В C++ термин «вызываемый» может иметь множество значений, особенно когда речь идет об указателях на элементы данных. Хотя номенклатура может поначалу показаться нелогичной, она помогает понять контекст и более широкое определение термина «вызываемый» в контексте C++. В определенном смысле указатели на элементы данных считаются вызываемыми. В основном это связано с тем, как к ним можно получить доступ, изменить их и взаимодействовать с использованием синтаксиса. По этой причине они считаются вызываемыми

Вот пример

#include <iostream>
using namespace std;

class MyClass {
public:
    int x;
    float y;
};

int main() {
    MyClass obj;
    obj.x = 10;
    obj.y = 3.14f;

    // Pointer to data member
    int MyClass::*pX = &MyClass::x;

    // Applying the pointer to the data member
    cout << "Value of x: " << obj.*pX << endl;  // Output: Value of x: 10

    return 0;
}

В этом коде obj.*pX выглядит и ведет себя как вызов функции, но на самом деле он обращается к элементу x объекта obj через указатель.

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

Должен ли std::variant быть неразрушимым, если его альтернатива потенциально может вызывать деструктор?
Перемещение после копирования при присвоении результата условного оператора
Может ли расширенный код встроенной функции различаться в двух единицах перевода?
Может ли быть нарушено ODR, если определение шаблона создается только с разными параметрами?
Является ли непостоянное нулевое целое число, приведенное к `void *`, по-прежнему нулевым указателем?
Требуется ли для создания экземпляров шаблонов классов использовать только указатель или ссылку на них?
Строгое псевдонимирование первого члена структуры через непрозрачный указатель в C
Определение макросов с тем же именем, что и функции стандартной библиотеки
Является ли передача ссылки на примитив из контекста C++ в контекст C неопределенным поведением?
Использование оператора безопасного вызова Kotlin

Похожие вопросы

Ошибка компиляции при использовании функций шаблона C++, которые принимают в качестве аргументов другие функции, которые принимают ссылки на указатели
Специализация шаблона вне встроенного пространства имен функции, определенной внутри встроенного пространства имен
Как я могу устранить неоднозначность вызова конструктора с параметром из создания именованного экземпляра?
Почему второй вызов std::distance дает разные результаты с std::list?
Почему NativeEvent не регистрирует нажатия клавиш?
Операторы моего производного класса не работают должным образом
Мне нужно перенести данные между моим временным виджетом и виджетом главного окна
Принудительное завершение работы ThreadSanitizer после обнаружения проблемы
Compare_exchange_weak() создает состояние гонки с приобретением-выпуском на x86
Ошибка: инициализатор для гибкого члена массива 'const char* ArgumentGuard::_args []'