Я смотрю на мотивацию статического оператора(), изложенную в этой статье.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1169r2.html
где они объясняют, что функциональные объекты, часто используемые в алгоритмах STL, несут дополнительные затраты на хранение своего указателя this в регистре, даже если оператор() его не использует.
Для этого они приводят следующий пример кода: https://godbolt.org/z/ajTZo2
Если запуск алгоритма для использования статической функции функционального объекта, а не нестатического оператора(), действительно приводит к более короткому ассемблерному коду. (Они запускают его один раз с -DSTATIC и один раз без )
struct X {
bool operator()(int) const;
static bool f(int);
};
inline constexpr X x;
int count_x(std::vector<int> const& xs) {
return std::count_if (xs.begin(), xs.end(),
#ifdef STATIC
X::f
#else
x
#endif
);
}
Однако, когда я изменяю функции, чтобы они действительно что-то делали, будь то просто возврат истины или сравнение параметра с жестко закодированным значением, различия, кажется, исчезают, и создается впечатление, что созданная сборка идентична, оставляя меня все еще искать преимущество статического оператора().
#include <vector>
#include <algorithm>
using namespace std;
struct X {
bool operator()(int data ) { return data > 5; } ;
static bool f(int data) { return data > 5; } ;
};
inline constexpr X x;
int count_x(std::vector<int> const& xs) {
return std::count_if (xs.begin(), xs.end(),
#ifdef STATIC
X::f
#else
x
#endif
);
}
может кто-нибудь объяснить?
Редактировать: После публикации этого вопроса и понимания из комментариев, что встраивание может быть причиной идентичной сборки, я изменил код на следующий, что снова делает сборку для статической версии более эффективной.
struct X {
bool operator()(int data ) ;
static bool f(int data);
};
__attribute__ ((noinline)) bool X::operator() (int data) {
{ return data > 5; }
}
__attribute__ ((noinline)) bool X::f(int data )
{ return data > 5; }
inline constexpr X x;
int count_x(std::vector<int> const& xs) {
return std::count_if (xs.begin(), xs.end(),
#ifdef STATIC
X::f
#else
x
#endif
);
}
Я еще этого не пробовал. Я подозревал встраивание и попробовал определить функции вне тела класса, но сборка по-прежнему идентична.
В нескольких файлах cpp?
Оригинальный пример — это, по сути, то, о чем говорит Джарод. Без тел на виду при составлении этого ТУ мы не можем просто опустить лишний аргумент.
А как насчет статических лямбда-операторов? Кажется, тела всегда будут на виду. Смогу ли я увидеть какую-либо разницу?
имхо на первое место надо поставить выразительность, еще и потому, что часто за ней следует эффективность. Зачем полагаться на то, что компилятор оптимизирует его, если нам вообще не нужен и не нужен этот экземпляр? Вы нашли один встречный пример, но если мы можем сделать его статическим, то независимо от того, что может сделать оптимизатор, накладные расходы на ненужный экземпляр отсутствуют.
Содержит ли пост и вопрос, и ответ? Здесь что-нибудь требует ответа?
Что касается меня, меня не особо волнует «использование регистра для передачи указателя this». По большому счету это ничего не значит. Вместо этого мне иногда просто нужен static operator(), чтобы я мог использовать синтаксис () вместо static perform() или чего-то подобного - иногда мне просто нужен синтаксис вызова функции для удобства чтения, а иногда нет.
@ChristianStieber: Хотя я не уверен, насколько это значимо. Нельзя Typename(). Ну, вы можете, но это означает «инициализировать объект этого типа с этими параметрами», как всегда. Поэтому он всегда будет называться object(). Если разрешить static operator(), читаемость не изменится.





Если функция встраивается, то, конечно, не имеет значения, каким будет фактическое соглашение о вызовах — она встраивается. Но функция не встраивается, тогда вам придется вызвать функцию, что в конечном итоге приведет к выполнению ненужной работы.
По сути, в этом суть возможности аннотировать операторов вызовов как static. Если вам не нужен параметр объекта, просто дайте понять из подписи, что параметр объекта вам не нужен. Вы уже можете сделать это с именованными функциями, просто вы не смогли бы этого сделать, если бы ваша функция-член имела имя (). Это было произвольное ограничение, которое не позволяло вам четко выразить свое намерение.
Кроме того, это упрощает работу компилятора - не нужно оптимизировать параметр объекта, если его изначально не было. Сбои при встраивании — это испытание, приводящее к смерти от тысячи сокращений, поэтому помогает каждая мелочь.
(Источник: это было мое предложение)
Спасибо. Я подумывал просто отправить вам этот вопрос по электронной почте, но подумал, что, возможно, здесь он может быть полезен другим.
При невидимом определении (разделённом на несколько файлов) компилятор не сможет удалить лишний
thisв нестатическом случае (если только LTO не увенчается успехом, но это сложнее).