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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With