Don't pay for what you don't use
I can imagine many situations where using the generator from one and only one thread would be quite reasonable. In that case, users would pay on each call to the function. It is one of the reasons why I prefer objects in these cases: it gives gun to hands of the users. Whether they shoot their foot or what they want to shoot is not my business any more. Though it is not excuse to make "easy to use incorrectly" interfaces.
C with classes interface
I'm not really sure why, but the exact kind of interfaces are prevalent. May be it is because I learnt modern C++ before learning anything else. Modern ways of doing things should come to mind first, and if they are not good fit, start searching for something else.
Modern (almost?) interface
Lets reconsider the interface (given you figured out how to share state, which leads to object):
template <typename ForwardIterator>
void generate_range(ForwardIterator first, ForwardIterator last)
//state sharing mechanism
while (first != last)
*first++ = number_distribution(random_number_generator);
This enables following syntax:
But this requires resizing. Some people even might want to print 10 integers into something, like in your example. Lets handle that too:
template <typename OutputIterator>
void generate_some(OutputIterator d_first, std::size_t n)
for (std::size_t i = 0; i < n; ++i)
*d_first++ = number_distribution(random_number_generator);
This enable following:
Or, if you want to append into vector:
And, just for symmetrical and intuitive interface:
Of course in all of the examples you'll get narrowing conversion warning, I was just too lazy to type out
std::uint64_t (I don't have IDE autocomplete here). Also, maybe putting
static_assert to check iterator types will output much better compilation error message.
Yes, yes, user experience. It is not only about graphical interfaces. What experience do you want your users to have? What use cases do they have? Usually you have a problem, then solve it. Creating an interface without use cases in mind is like solving non-existent problem.
I would design an interface using this algorithm:
Write usages out in declarative form
Write out some candidates
If list is empty, make usages more concrete, go back to 1
Narrow/order list based on "easy to use correctly, hard to use incorrectly"
Order the list based on implementation complexity
Start implementing from first to last, until the first working version
Think if they'll ever need more control, stop if not.
Port/factor out core functionality which has greatest amount of control, and base the interface on top of it. Usually template parameters with default arguments, function with default arguments,
using declarations help. I've yet to see the cases where it would be impossible.