Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic function using std::span doesn't compile

I wanted to make an isIn function that takes an std::span.

This is my attempt:

#include <span>

template <typename T1, typename T2>
bool isIn(const T1& x, std::span<const T2> v)
{
    for (const T2& e : v)
        if (e == x)
            return true;
    return false;
}

// this one would work, but I want my function to be generic
/*bool isIn(int x, std::span<const int> v)
{
    for (int e : v)
        if (e == x)
            return true;
    return false;
}*/

int main()
{
    const int v[] = {1, 2, 3, 4};
    isIn(2, v); // I want this, but doesn't compile
    //isIn(2, std::span<const int>(v)); // this works fine
}

As you can see, I can get around by doing this casting:

isIn(2, std::span<const int>(v));

But that's quite verbose, and I would like to do something like this:

isIn(2, v);

Is there any way that can be achived?

https://godbolt.org/z/czTs83

like image 623
tuket Avatar asked Oct 30 '25 08:10

tuket


2 Answers

Here is a c++20 version of your code.

First we start off with two concepts; is_span and spannable:

template<class T>
concept is_span = requires(T& a) {
    { std::span(a) } -> std::same_as<T>;
};
template<class T>
concept spannable = requires(T& a) {
    { std::span(a) };
} && !is_span<T>;

Something spannable can be deduced into a span without being one.

Then we write two overloads:

constexpr bool isIn(const auto& x, is_span auto const& v)
{
    for (const auto& e : v)
        if (e == x)
            return true;
    return false;
}

constexpr bool isIn(const auto& x, spannable auto const& v)
{
    return isIn(x, std::span(v));
}

using the new syntax.

We then add one more overload:

template<std::size_t N>
constexpr bool isIn(const auto& x, auto const(& v)[N])
{
    return isIn(x, std::span(v));
}

which permits this tasty syntax:

static_assert( isIn( 7, {1,2,3,4,5,6,7} ));

Live example

Now all you have to do is make it infix.

That "sadly" means

static_assert( isIn('\0', "hello") );

is true, as "hello" is an array containing a '\0' at the end.

template<class T>
constexpr bool isIn(const auto& x, std::initializer_list<T> il)
{
    return isIn(x, std::span(il));
}
template<std::size_t N>
constexpr bool isIn(char x, char const(& v)[N])
{
    return isIn(x, std::span(v, v+N-1));
}

Live example.

like image 69
Yakk - Adam Nevraumont Avatar answered Nov 01 '25 23:11

Yakk - Adam Nevraumont


There are no conversion/promotion for template deduction,

so const int (&)[4] cannot be deduced as std::span<const int /*, 4*/>.

You might still provide overload to do the conversion yourself (care to avoid infinite recursive call):

template <typename T1, typename T2, std::size_t N>
bool isIn(const T1& x, std::span<const T2, N> v)
{
    // return std::find(std::begin(v), std::end(v), x) != std::end(v);
    for (const T2& e : v) {
        if (e == x) {
            return true;
        }
    }
    return false;
}

template <typename T, typename C>
bool isIn(const T& x, const C&c)
{
    return isIn(x, std::span(c)); // Use CTAD for the conversion.
}

But std::span is not needed here:

template <typename T, typename C>
bool isIn(const T& x, const C& c)
{
    // return std::find(std::begin(c), std::end(c), x) != std::end(c);
    for (const auto& e : c) {
        if (e == x) {
            return true;
        }
    }
    return false;
}
like image 20
Jarod42 Avatar answered Nov 01 '25 22:11

Jarod42