c++c++-concepts

How can I define a concept that restricts based on the return value of a constexpr function?


Suppose I'd like to write a function that accepts anything I can iterate over with a size of exactly 2. How might I write a concept to do that? The closest I think I've gotten is the following:

#include <array>
#include <concepts>
#include <iostream>
#include <ranges>

template<typename T, size_t N>
concept RangeOfSize =
  std::ranges::sized_range<T> &&
  requires (T i) { i.size() == N; }
;

void f(RangeOfSize<2> auto&& r) { std::cout << r.size() << std::endl; }

int main() {
    f(std::array<uint8_t, 2>{2, 3});
    f(std::array<uint8_t, 3>{5, 7, 11});
}

g++ -std=c++23 $0 -o exe -Wall -Werror -Wextra -pedantic-errors

Which compiles fine, much to my chagrin, and outputs

2
3

Ideally it would fail to compile on the second call to f because that array is not 2 elements long. HIGHLY ideally it would fail with a nice-to-read error but I'm giving up hope on that.

(Bonus points for an explanation on how I might have figured out the answer myself).


Solution

  • You can just use a static_assert to accomplish this:

    void f(auto&& r) 
    { 
        static_assert(r.size() == 2, "Only ranges of size 2 supported");
        std::cout << r.size() << std::endl; 
    }
    

    For

    f(std::array<int, 3>{5, 7, 11});
    

    GCC gives the follwing output:

    <source>: In instantiation of 'void f(auto:10&&) [with auto:10 = std::array<int, 3>]':
    <source>:18:6:   required from here
       18 |     f(std::array<int, 3>{5, 7, 11});
          |     ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    <source>:12:28: error: static assertion failed: Only ranges of size 2 supported
       12 |     static_assert(r.size() == 2, "Only ranges of size 2 supported");
          |                   ~~~~~~~~~^~~~
    <source>:12:28: note: the comparison reduces to '(3 == 2)'
    Compiler returned: 1