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

Concept for floating-point contiguous range in C++20


I'm new to C++20 concepts and I'm struggling to define a concept for a floating-point contiguous range. I managed to write a concept that works more or less but I cannot use it to enforce a specific floating-point type such as float or double.

Here is my first attempt:

#include <concepts>
#include <iostream>
#include <ranges>
#include <vector>

template<class R>
concept FloatingPointRange = 
    std::ranges::contiguous_range<R> && 
    std::floating_point<std::ranges::range_value_t<R>>;

template<std::floating_point F>
class C {
  public:
    explicit C(F x) : x_{x} {}

    void add(FloatingPointRange auto const& r) {
        for (auto const& v : r) {
            x_ += v;
        }
    }
    
    F get() {
        return x_;
    }

  private:
    F x_;
};

void print(FloatingPointRange auto const& r) {
    for (const auto& v: r) {
        std::cout << v << "  ";
    }
    std::cout << "\n";
}

int main() {
    std::vector<float> v = {100, 100};

    C<float> c{10};
    std::cout << c.get() << "\n";  // This line prints "10".

    c.add(v);
    std::cout << c.get() << "\n"; // This line prints "210".

    print(v);  // This line prints "100 100".

    return 0;
}

However, this concept also works when passing a vector of double values to the add function of the C class instead of causing a compilation error as shown below.

std::vector<double> w = {1000, 1000, 1000, 1000};
c.add(w); // This works but I would like to get a compilation error because F = float here.

So in a second attempt I tried to use a parametrized concept instead. This indeed causes a compilation error for the add function of the C class if the floating-point type does not match. However, it breaks the print function.

#include <concepts>
#include <iostream>
#include <ranges>
#include <vector>

template<class R, class F>
concept FloatingPointRange = 
    std::ranges::contiguous_range<R> && 
    std::floating_point<F> &&
    std::same_as<std::ranges::range_value_t<R>, F>;

template<std::floating_point F>
class C {
  public:
    explicit C(F x) : x_{x} {}

    void add(FloatingPointRange<F> auto const& r) {
        for (auto const& v : r) {
            x_ += v;
        }
    }
    
    F get() {
        return x_;
    }

  private:
    F x_;
};

template<std::floating_point F>
void print(FloatingPointRange<F> auto const& r) {
    for (auto const& v: r) {
        std::cout << v << "  ";
    }
    std::cout << "\n";
}

int main() {
    std::vector<float> v = {100, 100};
    std::vector<double> w = {1000, 1000, 1000, 1000};

    C<float> c{10};
    std::cout << c.get() << "\n";  // This line prints "10"

    c.add(v);
    std::cout << c.get() << "\n";  // This line prints "210"

    c.add(w);  // This line causes a compilation error as expected.


    print(v);  // This line causes a compilation error.
    print(w);  // Same error as the previous line.

    return 0;
}

With this approach I get a compilation error when calling c.add(w) as expected. However, I also get a compilation error for print(v) and print(w) (template argument deduction/substitution failed).

You can find these two attempts here: https://godbolt.org/z/b8f4PvE3G (you can comment the #define GENERIC line at the beginning to switch between my two attempts).

To summarize, I'm stuck on the way to define a contiguous range of floating-point values that would both reject calls to C::add with the wrong floating-point type and accept calls to print for all floating-point types. So I would be grateful if anyone could help me to solve my issue.


Solution

  • You can define a default template parameter for the FloatingPointRange concept

    template<class R, class F = std::ranges::range_value_t<R>>
    concept FloatingPointRange = 
        std::ranges::contiguous_range<R> && 
        std::floating_point<F> &&
        std::same_as<std::ranges::range_value_t<R>, F>;
    

    and make print only constrain the argument to be a FloatingPointRange for which F is not explicitly provided, which can accept any range of floating-point types:

    void print(FloatingPointRange auto const& r) {
        for (auto const& v: r) {
            std::cout << v << "  ";
        }
        std::cout << "\n";
    }
    

    Demo