3

I was using a std::uniform_int_distribution to generate primes (p). I put the distribution object in an anonymous namespace - which seems like C++ 'static linkage' for grown-ups...

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

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

Note, that I seed the Mersenne Twister as thoroughly as portable code will allow. This isn't really important to the question though. It's just to assure the reader I'm using the random facilities properly:

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

This is very convenient, as I have a deterministic u64_prime(p) function, using (7) bases, that can determine if (p) is prime:

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

Now I create a std::function object:

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

This function: zp_rng() can be invoked to return a random number in Z(p). That is, using the distribution object for: [0, p - 1] from the results of the referenced rng.


Now this is very impressive - but I've effectively adopted it by cut-and-paste with little understanding of the interaction between std::function and the interaction of the parameters given to std::bind.

I'm not confused by decltype(p_dist){0, p - 1} - that's just a way to specify we still want to use a std::uniform_int_distribution. My understanding of std::ref(rng) is that it prevents a local copy of the rng being instantiated, and forces the use of reference instead... so:


Q: What are the basic rules that effectively determine: dist(rng) being used - I don't see why std::bind would enforce this interaction. A lot of interactions seem based around operator () methods.

Q: std::function is helpfully referred to as 'a general-purpose polymorphic function wrapper' on cppreference.com. So is it a function that encapsulates a uint64_t return type? Or again, making use of operator () syntax to drive the notion of a function?

As incredibly useful as these constructs are, I feel like I'm cargo-cult programming to a degree here. I'm looking for an answer which resolves any ambiguities in a concrete way, and adds insight to similar questions - how are bind arguments expected to interact, and how does the function signature reflect that?


I'm not getting any positive feedback about the use of std::bind. Plenty on the superior results (and code generation) of simply using lambda functions, even in such a simple case. My own tests validate this.

Brett Hale
  • 21,653
  • 2
  • 61
  • 90
  • Not an answer, but a tip: `std::function` is overkill here, you can use a lambda instead, which also allows you to get rid of `std::bind` :-) – Acorn Jul 06 '19 at 19:09
  • @Acorn - you may be right, but in this case, I can't help but feel a lambda would be another level of obfuscation prior to understanding the underlying mechanism(s). – Brett Hale Jul 06 '19 at 19:12
  • I don't really understand the questions. Are you basically asking what `std::bind` and `std::function` are? – Barry Jul 09 '19 at 12:31
  • FWIW you really shouldn't use a `std::function` unless you need to hold on to the functor as a class member. If not then you should use `auto` to get the return type from `bind` and use that object. `std::function` uses type erasure which is costly and should be avoided (and generally can be outside of class members). – NathanOliver Jul 09 '19 at 13:19
  • @NathanOliver - the more feedback I get, and the more examples I see, the more `std::bind` feels like a hack. No one has provided any example of `std::bind` being best practice... – Brett Hale Jul 09 '19 at 14:12
  • @BrettHale That is because `std::bind` can be replaced with a lambda, and lambdas make it more intuitive (IMHO). `bind` was good idea for continuing the function interface that C++98 started but with lambdas, it turned out it wasn't needed and can often times be more verbose. One big issue is with references. Lets say you have a function you wrap with `bind` that has reference parameters, you need to use `std::ref`/`std::cref` in the calls site like `auto func = std::bind(some_func, std::ref(var1), std::ref(var2));`. – NathanOliver Jul 09 '19 at 14:17
  • If you use a lambda then the code just becomes `auto func = [&](){ return some_func(var1, var2); };` – NathanOliver Jul 09 '19 at 14:17
  • @NathanOliver - To be clear, I wouldn't need `std::ref` at all in a lambda, as I can simply specify the `rng` as a reference with natural syntax? – Brett Hale Jul 09 '19 at 14:21
  • @NathanOliver - You're not wrong about the cost - in terms of code generation at least! Even for simple cases, it's very apparent under both gcc and clang. – Brett Hale Jul 09 '19 at 14:29
  • @BrettHale Exactly. Your example could be replaced with `auto zp_rng = [&](){ return decltype(p_dist){0, p - 1}(rng); }` and now `zp_rng` is some class type with an overloaded `operator()` that when called executes `decltype(p_dist){0, p - 1}(rng);`. – NathanOliver Jul 09 '19 at 14:29
  • This makes it prime for inlining which can be a big performance gain. Since `std::uniform_int_distribution` really isn't statefult, you don't need to worry about creating one every time you call it. If you are using something that has state, then declare it outside the lambda and then capture it, or declare it static inside the lambda, I normally use this method, so it is only created once. – NathanOliver Jul 09 '19 at 14:32
  • @NathanOliver - that is exactly what I had done:) The code size difference was staggering, and that's for one relatively simple case. – Brett Hale Jul 09 '19 at 14:32
  • 1
    Lambdas FTW \o/ – NathanOliver Jul 09 '19 at 14:32
  • @NathanOliver - with your rep level, you probably couldn't be bothered to write up an answer about the merits of lambdas vs. `std::bind` - even with a bounty? – Brett Hale Jul 09 '19 at 14:41
  • 1
    There is actually a dupe for that [here](https://stackoverflow.com/questions/17363003/why-use-stdbind-over-lambdas-in-c14). I didn't want to basically rewrite those great answers. I did want to make you aware of the situation. – NathanOliver Jul 09 '19 at 14:42
  • @NathanOliver - thanks for that. There's no technical reason for me not to be using C++17 at present. – Brett Hale Jul 09 '19 at 14:48

4 Answers4

6

std::bind is kind of irrelevant to std::function, all it does is wrap something callable so that a certain set of parameters is always passed, see https://en.cppreference.com/w/cpp/utility/functional/bind

std::function just takes anything callable which fits the function signature that you specify as its template arguments.

std::uniform_int_distribution is callable because it has a operator() specified, see https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution/operator() . That specifically takes a Generator. We use bind to create a wrapper around that so that we don't always have to pass the generator explicitly. The result is then stored in the std::function matching the uint64_t return type and no arguments (as the generator argument is bound).

If this concept is foreign to you, you should read up on operator overloading, see https://en.cppreference.com/w/cpp/language/operators (specifically Function call operator).

Stephan Dollberg
  • 32,985
  • 16
  • 81
  • 107
  • As I've said - there's no hurry to be first. You've already provided some conceptual points, and can elaborate for the bounty. Just because `std::uniform_int_distribution` is callable because it has a `operator ()` doesn't really explain why that's with the `rng` binding. What if there were another object in the binding, etc.? – Brett Hale Jul 06 '19 at 19:20
  • `std::bind` just creates a wrapped function which always passes the bound argument - rng in your case. It's just yet another function object. – Stephan Dollberg Jul 06 '19 at 19:55
3

There is no need to use std::bind in modern C++ - use a lambda instead:

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

As for std::bind and std::function instances, they are callable function objects, i.e. they have operator().

Igor R.
  • 14,716
  • 2
  • 49
  • 83
  • This syntax actually makes more sense to me. The operation of the `dist` on the `rng` is explicit. But can `zp_rng` really be considered a lambda, just because it's implementation is? – Brett Hale Jul 06 '19 at 19:24
  • Actually the use of `std::bind` with its 'placeholder' syntax reminds me of the worst hacks before variadic templates... – Brett Hale Jul 06 '19 at 19:31
  • 1
    @Brett Hale `zp_rng` is an instance of `std::function`, not a lambda. The lambda is on the right side of `=`. It is possible, because `function` can be initialized with any callable, including lambda, "binder", function pointer, custom functional object - as long as they have a compatible `operator()`. – Igor R. Jul 06 '19 at 20:16
  • 1
    @BrettHale The placeholder syntax isn't a hack before variadic templates. `std::bind()` _is_ a variadic template. – Barry Jul 09 '19 at 12:28
  • @Barry - perhaps the worst of both worlds? I'm not getting any feedback with anything positive to say about `std::bind`. – Brett Hale Jul 09 '19 at 14:16
  • The placeholder syntax is orthogonal to variadic templates, it's not a hack to work without them. For a start the number of placeholders in a given bind expression is fixed, not variable. How would you use variadic templates to say "do this with the first argument, and do this with the second"? – Jonathan Wakely Jul 09 '19 at 14:47
  • @JonathanWakely - I don't understand your point here. Since variadic templates are excluded, you expect me to come up with a use case? If I could do that, why would I have even asked a question that most advanced users seem to find basic? – Brett Hale Jul 09 '19 at 15:01
  • 1
    @BrettHale Huh? The worst of what worlds? Is the source of confusion that you're unsure of what `std::bind` actually is? – Barry Jul 09 '19 at 15:32
  • @BrettHale "Since variadic templates are excluded" -- I didn't say that. If bind placeholders are a hack from a time before variadic templates it suggests they're unnecessary now we have variadic templates ... so how would you use variadic templates to do what `std::bind` does, replacing the placeholder syntax? Complaining that the syntax is a hack from the time before **lambdas** makes sense. Complaining it's a hack from before variadic templates is like saying apples are a hack from before we had oranges. – Jonathan Wakely Jul 09 '19 at 15:44
  • @JonathanWakely - I understand now. So it was very much an interim measure - no doubt with its roots in Boost? Thanks for adding a very definitive answer, btw. – Brett Hale Jul 09 '19 at 17:06
3

Q: What are the basic rules that effectively determine: dist(rng) being used - I don't see why std::bind would enforce this interaction. A lot of interactions seem based around operator () methods.

std::bind performs function composition. The first argument must be a function object, i.e. something callable like a function (e.g. a normal function, or a class with an overloaded operator()).

A call to std::bind makes copies of its arguments, "binds" the copies of the arguments to the first argument (the function object), and returns a new function object that will invoke the copy of the function object.

So in a simple case:

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

this binds the value 1 to the function f, creating a new function object that can be called with no arguments. When you invoke f1() it will invoke f with the argument 1, i.e. it will call f(1), and return whatever that returns (which in this case is just 1).

The actual type of the thing returned by std::bind(f, 1) is some implementation-specific class type, maybe called something like std::__detail::__bind_type<void(*)(int), int>. You're not meant to refer to that type directly, you would either capture the object using auto or store it in something else that doesn't care about the precise type, so either:

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

or:

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

In your more complex case, when you call std::bind(decltype(p_dist){0, p - 1}, std::ref(rng))) you get a new function object that contains a copy of the temporary decltype(p_dist){0, p - 1} and a copy of the reference_wrapper<std::mt19937> created by std::ref(rng). When you invoke that new function object it will call the contained distribution, passing it a reference to rng.

That means it is callable with no arguments, and it will call the contained random number distribution with the contained random engine, and return the result.

Q: std::function is helpfully referred to as 'a general-purpose polymorphic function wrapper' on cppreference.com. So is it a function that encapsulates a uint64_t return type?

A std::function<uint64_t()> is a wrapper for a function object that is callable with no arguments and that returns a uint64_t (or something implicitly convertible to uint64_t). It can be used to store a copy of an arbitrary function object that is copy constructible, and callable with no arguments, and returns something convertible to uint64_t.

(And more generally, a std::function<R(A1, A2 ... AN)> is a wrapper for a function object that returns R when called with N arguments, of types A1, A2 ... AN.)

Since the result of your std::bind call is a copy constructible function object that is callable with no arguments and returns a uint64_t, you can store the result of that std::bind call in a std::function<uint64_t()>, and when you invoke the function it will invoke the result of the bind call, which will invoke the contained distribution, and return the result.

Or again, making use of operator () syntax to drive the notion of a function?

I'm not sure what this means, but in general it's true that in C++ we often talk about "function objects" or "callables" which are generalisations of functions, i.e. something that can be invoked using function call syntax, e.g. a(b, c)

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
0

I'm quoting from A Tour of C++ what is bind():

A function adaptor takes a function as argument and returns a function object that can be used to invoke the original function. The standard library provides bind() and mem_fn() adaptors to do argument binding, also called Currying or partial evaluation. Binders were heavily used in the past, but most uses seem to be more easily expressed using lambdas.

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

A call cube2() will invoke cube with the argument 2, that is, cube(2)

A bind() can be used directly, and it can be used to initialize an auto variable. In that, bind() resembles a lambda.

If we want to assign the result of bind() to a variable with a specific type, we can use the standard library type function. A function is specified with a specific return type and a specific argument type.

The standard library function is a type that can hold any object you can invoke using the call operator (). That is, an object of type function is a function object.

So std::function is a function object. A function object is used to define objects that can be called like functions. and this is an example of function object:

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

And

auto cube2 = bind(cube,2); is like saying Cube2<int> cube2{2};

Oblivion
  • 7,176
  • 2
  • 14
  • 33