Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would the param_type constructor be explicit for a random distribution?

I'm trying to compile this program (see it live here):

int main() {
  std::random_device engine;
  std::uniform_int_distribution<size_t> dis;
  std::cout << dis(engine, {0, 5}) << std::endl;
}

But it fails with the error message:

error: converting to 'const std::uniform_int_distribution<long unsigned int>::param_type' from initializer list would use explicit constructor 'std::uniform_int_distribution<_IntType>::param_type::param_type(_IntType, _IntType) [with _IntType = long unsigned int]'
   std::cout << dis(engine, {0, 5}) << std::endl;

Obviously, it is the explicit constructor of param_type that prevents us from doing this. But why specifying it as explicit in the first place? It's verbose and silly if one has to write

std::cout << dis(engine, decltype(dis)::param_type(0, 5)) << std::endl;

Any explanations on this? And also, how could I achieve what I want in a succinct and elegant way, given that the param_type constructor is explicit in the standard? Note that, in practice, the range may be different each time I invoke dis. So, supplying a range at the construction time of dis does not help.

like image 884
Lingxi Avatar asked Mar 22 '26 19:03

Lingxi


1 Answers

It is explicit because it has defaulted arguments. Any constructor taking 1 argument that isn't intended to be a converting constructor should be explicit.

Because the standard mandates one constructor with two defaulted arguments, all cases are explicit. An improvement would be to mandate a 2 argument constructor that is not explicit, an explicit 1 argument constructor, and a non-explicit 0 argument constructor. But that ship has sailed.

We can work around your problem.

template<class...Args>
struct construct_params_t {
  std::tuple<Args&&...> data;
  construct_params_t(Args&&...args):data(std::forward<Args>(args)...){}

private:
  template<class T, std::size_t...Is>
  T create( std::index_sequence<Is...> ){
    return T( std::get<Is>(std::move(data))... );
  }
public:
  template<class T>
  operator T()&&{
    return create<T>( std::index_sequence_for<Args...>{} );
  }
};
template<class...Args>
construct_params_t<Args...> construct_from(Args&&...args) {
  return {std::forward<Args>(args)...};
}

now you can do:

int main() {
  std::random_device engine;
  std::uniform_int_distribution<size_t> dis;
  std::cout << dis(engine, construct_from(0, 5)) << std::endl;
}

basically, construct_from(a,b,c) can be used to construct almost anything, and it does so explicitly, without having to mention its name when constructing it.

Live example.

The objects thus constructed must be movable, and with RVO elision it shouldn't actually move them.

Storing the return value of construct_from is not advised.

like image 93
Yakk - Adam Nevraumont Avatar answered Mar 25 '26 09:03

Yakk - Adam Nevraumont



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!