Search code examples
c++c++20c++-concepts

Why is my use of std::uniform_random_bit_generator failing?


Given the following sample program, which makes use of the std::uniform_random_bit_generator concept:

#include<random>
#include<print>

template<std::uniform_random_bit_generator PRNG>
void test(PRNG && rng) {
    std::uniform_int_distribution<int> dist{1, 7};
    std::print("{}\n", dist(rng));
}

int main() {
    std::minstd_rand engine{std::random_device{}()};
    test(engine);
}

I expect this code to compile, but it fails with the following error messages.

What confuses me is that I haven't defined any new types: this uses a built in C++ random bit generator, and I get the same error when using other generators (mt19937, etc.). Why is this happening? Am I applying the concept incorrectly?

Clang:

<source>:12:5: error: no matching function for call to 'test'
   12 |     test(engine);
      |     ^~~~
<source>:5:6: note: candidate template ignored: constraints not satisfied [with PRNG = std::minstd_rand &]
    5 | void test(PRNG && rng) {
      |      ^
<source>:4:10: note: because 'std::linear_congruential_engine<unsigned long, 48271, 0, 2147483647> &' does not satisfy 'uniform_random_bit_generator'
    4 | template<std::uniform_random_bit_generator PRNG>
      |          ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/15.0.0/../../../../include/c++/15.0.0/bits/uniform_int_dist.h:57:4: note: because '_Gen::min()' would be invalid: type 'std::linear_congruential_engine<unsigned long, 48271, 0, 2147483647> &' cannot be used prior to '::' because it has no members
   57 |         { _Gen::min() } -> same_as<invoke_result_t<_Gen&>>;
      |           ^
1 error generated.
Compiler returned: 1

GCC 14.2:

<source>: In function 'int main()':
<source>:12:9: error: no matching function for call to 'test(std::minstd_rand&)'
   12 |     test(engine);
      |     ~~~~^~~~~~~~
<source>:5:6: note: candidate: 'template<class PRNG>  requires  uniform_random_bit_generator<PRNG> void test(PRNG&&)'
    5 | void test(PRNG && rng) {
      |      ^~~~
<source>:5:6: note:   template argument deduction/substitution failed:
<source>:5:6: note: constraints not satisfied
In file included from /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/random.h:35,
                 from /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/random:48,
                 from <source>:1:
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/uniform_int_dist.h: In substitution of 'template<class PRNG>  requires  uniform_random_bit_generator<PRNG> void test(PRNG&&) [with PRNG = std::linear_congruential_engine<long unsigned int, 48271, 0, 2147483647>&]':
<source>:12:9:   required from here
   12 |     test(engine);
      |     ~~~~^~~~~~~~
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/uniform_int_dist.h:53:13:   required for the satisfaction of 'uniform_random_bit_generator<PRNG>' [with PRNG = std::linear_congruential_engine<long unsigned int, 48271, 0, 2147483647>&]
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/uniform_int_dist.h:55:10:   in requirements  [with _Gen = std::linear_congruential_engine<long unsigned int, 48271, 0, 2147483647>&]
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/uniform_int_dist.h:57:20: note: the required expression '_Gen::min()' is invalid
   57 |         { _Gen::min() } -> same_as<invoke_result_t<_Gen&>>;
      |           ~~~~~~~~~^~
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/uniform_int_dist.h:58:20: note: the required expression '_Gen::max()' is invalid
   58 |         { _Gen::max() } -> same_as<invoke_result_t<_Gen&>>;
      |           ~~~~~~~~~^~
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/uniform_int_dist.h:59:18: note: nested requirement 'std::bool_constant<((bool)(_Gen::min() < _Gen::max()))>::value' is not satisfied
   59 |         requires bool_constant<(_Gen::min() < _Gen::max())>::value;
      |         ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1plus: note: set '-fconcepts-diagnostics-depth=' to at least 2 for more detail
Compiler returned: 1

Solution

  • This is a tricky and a bit confusing C++ part even for me. So after a check on the <random> documentation and C++20 Concepts, here is why:

    The Problem

    The uniform_random_bit_generator concept is failing because of two related issues:

    • Reference & Type Problem: When you use PRNG&& in your template, C++ tries to check the concept requirements on the reference type (like minstd_rand&), not the actual type (minstd_rand).
    • Static Range Requirements: The concept requires static min() and max() functions because they define the range of possible values any instance of that generator type can produce. They're static because these bounds are properties of the generator type itself, not of specific instances (like how minstd_rand always generates between 0 and 2147483647).

    Solution

    We have to either pass PRNG directly instead of passing it with a forwarding reference or work around the Concept Checks.

    For the second solution, we would use this: std::remove_reference_t

    So it would be something like this:

        template<typename PRNG>
        requires std::uniform_random_bit_generator<std::remove_reference_t<PRNG>>
        void test(PRNG && rng)
    

    Hope this was a helpful answer. Concepts are still a challenging topic for me in C++.