You can use a filter of arbitrary complexity on uniform distribution:
template<typename D, typename G, typename F>
auto sample(D &distribution, G &generator, F const &filter)
{
while(true)
{
auto const value = distribution(generator);
if(filter(value))
return value;
}
}
Your example case transforms into the following
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> probability(0, 100);
auto const filter = +[](int n) {return n < 4 || n > 7;}
int const i = sample(probability, mt, filter);
You have to keep in mind that this kind of filtering comes at a cost.
Let N be the number of distinct values the distribution returns, F - the number of these values filtered out; then, if you need to sample S values, you have to sample and filter S * N / (N - F) values at average. It's okay if F is small compared to N, but horribly inefficient when F approaches N. In your case, N = 100, F = 4, and N / (N - F) = 1.04166...
If you care prefer readability and simplicity, that's your choice. Otherwise, if you need performance, you'd better try out piecewise distributions or mess with the value range manually.