std :: function прекрасно работает с std :: bind - но почему? - PullRequest
3 голосов
/ 06 июля 2019

Я использовал std::uniform_int_distribution для генерации простых чисел (p). Я поместил объект дистрибутива в анонимное пространство имен - что похоже на «статическую связь» C ++ для взрослых ...

namespace
{
    // a more pedantic range: [2, 18446744073709551557]
    std::uniform_int_distribution<uint64_t> p_dist {2};

    std::mt19937 rng; // (sufficient IV states for uniqueness)
}

Обратите внимание, что я заполняю Mersenne Twister настолько тщательно, насколько позволяет переносимый код. Это не очень важно для вопроса, хотя. Я просто хочу убедить читателя, что я правильно использую случайные средства:

std::seed_seq::result_type data[rng.state_size];
std::random_device rdev;

std::generate_n(data, rng.state_size, std::ref(rdev));
std::seed_seq rng_seed (data, data + rng.state_size);
rng.seed(rng_seed);

Это очень удобно, так как у меня есть детерминированная функция u64_prime(p), использующая (7) bases, которая может определить, является ли (p) простым:

uint64_t p;
while (!u64_prime(p = p_dist(rng)))
    ;

Теперь я создаю std::function объект:

std::function<uint64_t()> zp_rng = std::bind(
    decltype(p_dist){0, p - 1}, std::ref(rng));

Эта функция: zp_rng() может быть вызвана для возврата случайного числа в Z(p). То есть, используя объект распространения для: [0, p - 1] из результатов ссылки rng .


Теперь это очень впечатляет - но я эффективно применил его путем вырезания и вставки с небольшим пониманием взаимодействия между std::function и взаимодействием параметров, заданных std::bind.

Меня не смущает decltype(p_dist){0, p - 1} - это просто способ указать, что мы все еще хотим использовать std::uniform_int_distribution. Насколько я понимаю, std::ref(rng) состоит в том, что он предотвращает создание локальной копии rng и заставляет вместо этого использовать ссылку ... так:


Q : Каковы основные правила, которые эффективно определяют: dist(rng) используется - я не понимаю, почему std::bind будет обеспечивать это взаимодействие. Кажется, что многие взаимодействия основаны на operator () методах.

Q : std::function часто упоминается как «обертка полиморфной функции общего назначения» на cppreference.com . Так это функция, которая инкапсулирует uint64_t возвращаемый тип? Или снова использовать синтаксис operator () для обозначения функции?

Как бы невероятно ни были полезны эти конструкции, я чувствую, что в какой-то степени я занимаюсь культовым программированием. Я ищу ответ, который решает любые двусмысленности конкретным образом и добавляет понимание к подобным вопросам - как ожидается взаимодействие bind аргументов, и как подпись function отражает это?


Я не получаю каких-либо положительных отзывов об использовании std::bind. Множество превосходных результатов (и генерации кода) простого использования лямбда-функций, даже в таком простом случае. Мои собственные тесты подтверждают это.

Ответы [ 4 ]

6 голосов
/ 06 июля 2019

std::bind отчасти не имеет отношения к std::function, все, что он делает, это оборачивает что-то вызываемое так, чтобы всегда передавался определенный набор параметров, см. https://en.cppreference.com/w/cpp/utility/functional/bind

std::function просто принимает все вызываемоекоторая соответствует сигнатуре функции, которую вы указываете в качестве аргументов шаблона.

std::uniform_int_distribution вызывается, потому что для нее задано operator(), см. https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution/operator().Это специально берет генератор.Мы используем связывание, чтобы создать обертку вокруг этого, чтобы нам не всегда приходилось явно передавать генератор.Затем результат сохраняется в std::function, соответствующем типу возвращаемого значения uint64_t, и без аргументов (поскольку аргумент генератора связан).

Если это понятие вам чуждо, вам следует прочитать о перегрузке операторов, см.https://en.cppreference.com/w/cpp/language/operators (в частности, оператор вызова функций).

3 голосов
/ 09 июля 2019

Q: Каковы основные правила, которые эффективно определяют: используется dist (rng) - я не понимаю, почему std :: bind будет обеспечивать это взаимодействие.Кажется, что многие взаимодействия основаны на методах operator ().

std::bind выполняет составление функций .Первый аргумент должен быть объектом функции, то есть чем-то вызываемым , например, функцией (например, нормальной функцией или классом с перегруженным operator()).

Вызов std::bind создает копии своих аргументов, «привязывает» копии аргументов к первому аргументу (объекту функции) и возвращает новый объект функции, который будет вызывать копию функцииobject.

Таким образом, в простом случае:

int f(int i) { return i; }
auto f1 = std::bind(f, 1);

это связывает значение 1 с функцией f, создавая новый объект функции, который можно вызывать без аргументов.Когда вы вызываете f1(), он вызывает f с аргументом 1, то есть он вызывает f(1) и возвращает все, что возвращает (что в данном случае просто 1).

Фактический тип вещи, возвращаемой std::bind(f, 1), является неким типом класса, зависящим от реализации, который может называться как std::__detail::__bind_type<void(*)(int), int>.Вы не должны ссылаться на этот тип напрямую, вы бы либо захватили объект с помощью auto, либо сохранили бы его в другом месте, не обращающем внимания на точный тип, поэтому либо:

auto f1 = std::bind(f, 1);

или:

std::function<int()> f1 = std::bind(f, 1);

В более сложном случае, когда вы вызываете std::bind(decltype(p_dist){0, p - 1}, std::ref(rng))), вы получаете новый функциональный объект, который содержит копию временного decltype(p_dist){0, p - 1} и копию reference_wrapper<std::mt19937>, созданную std::ref(rng).Когда вы вызываете этот новый объект функции, он вызывает вызываемый дистрибутив, передавая ему ссылку на rng.

Это означает, что он может вызываться без аргументов, и он будет вызывать распределение случайных чисел с содержимымслучайный механизм и возвращает результат.

Q: std::function часто упоминается как «обертка полиморфной функции общего назначения» на cppreference.com.Так это функция, которая инкапсулирует тип возвращаемого значения uint64_t?

A std::function<uint64_t()> - это обертка для объекта функции, которая вызывается без аргументов и возвращает uint64_t (или что-то в этом роде)неявно конвертируется в uint64_t).Он может быть использован для хранения копии произвольного объекта функции, который может быть создан с возможностью копирования и без аргументов, и возвращает что-то, что можно преобразовать в uint64_t.

(И, в общем, std::function<R(A1, A2 ... AN)> - этооболочка для объекта функции, который возвращает R при вызове с N аргументами типов A1, A2 ... AN.)

, поскольку результатом вашего вызова std::bind являетсяскопировать объект конструируемой функции, который вызывается без аргументов и возвращает uint64_t, вы можете сохранить результат этого вызова std::bind в std::function<uint64_t()>, а когда вы вызываете function, он вызовет результат bind вызов, который вызовет содержащийся в нем дистрибутив и вернет результат.

Или снова, используя синтаксис operator () для определения понятия функции?

Я не уверен, что это значит, но в целом верно, что в C ++ мы часто говорим о «объектах функций» или «вызываемых объектах», которые являются обобщениями функций, то есть что-то, что может быть вызвано с помощью fuСинтаксис вызова nction, например a(b, c)

3 голосов
/ 06 июля 2019

Нет необходимости использовать std::bind в современном C ++ - вместо этого используйте лямбду:

// NOTE: p and rng are captured by reference and must outlive zp_rng!
std::function<uint64_t()> zp_rng = [&]() { return decltype(p_dist){0, p - 1}(rng); };

Что касается std :: bind и std :: function , они являются вызываемыми функциональными объектами, то есть имеют operator().

0 голосов
/ 06 июля 2019

Я цитирую Тур C ++ что такое bind():

Адаптер функции принимает функцию в качестве аргумента и возвращает функцию объект, который можно использовать для вызова исходной функции. Стандарт библиотека предоставляет адаптеры bind() и mem_fn() для аргументов переплет, также называемый Curry или частичная оценка . Связующие были интенсивно использовался в прошлом, но большинство применений, кажется, легче выражается в лямбдах.

double cube(double);
auto cube2 = bind(cube,2);

Вызов cube2() вызовет cube с аргументом 2, то есть cube(2)

A bind() может использоваться напрямую, и его можно использовать для инициализации автоматическая переменная. В этом bind() напоминает лямбду.

Если мы хотим присвоить результат bind() переменной с конкретный тип, мы можем использовать стандартную библиотеку типа function. function указывается с конкретным типом возврата и конкретным тип аргумента.

Стандартная библиотека function - это тип, который может содержать любой объект, который вы можно вызвать с помощью оператора вызова (). То есть объект типа function является function object.

То есть std::function является функциональным объектом. объект функции используется для определения объектов, которые можно вызывать как функции. и это пример объекта функции:

template<typename T>
class Cube2 {
const T val; // value to compare against
public:
Cube2 (const T& v) :val(v) { }
double operator()(const T& x) const { return pow(x,3); } // call operator
};

И

auto cube2 = bind(cube,2); все равно что сказать Cube2<int> cube2{2};

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...