Andreas0815
Andreas0815

Reputation: 27

C++ How to define the spaceship operator for a template class?

Im trying to define the spaceship operator for a simple template string class.

namespace mylib
{
    template <size_t N>
    struct String
    {
        String()
        {
            *data = '\0';
        }
    
        String(const char *str)
        {
            size_t l = strlen(str);
    
            if (l >= N)
                throw std::runtime_error(str);
    
            memcpy(data, str, l + 1);
        }
    
        auto operator<=>(const String& str) const
        {
            return data <=> str.data;
        }
    
        char data[N];
    };
}
    
    void test_string()
    {
        mylib::String<16> str1;
        mylib::String<16> str2("Hallo");
        mylib::String<16> str3 = "Hallo";
        mylib::String<16> str4 = "hallo";
        assert(str2 == str3);
        assert(str4 != str2);
        assert(str2 < str4);
        assert(str2 <= str4);
        assert(str4 > str2);
        assert(str4 >= str2);
    
        mylib::String<3> str5 = "Hallo";
    }

But i get some errors.

1>E:\Projects\Windows\ind\IndDlg.cpp(104,2): error C2678: binary '==': no operator found which takes a left-hand operand of type 'mylib::String<16>' (or there is no acceptable conversion)
1>        'bool operator ==(const D2D1_SIZE_U &,const D2D1_SIZE_U &)': cannot convert argument 1 from 'mylib::String<16>' to 'const D2D1_SIZE_U &'

How to define the spaceship operator for a template class? It should be able to compare strings of different size: String<16> < String<32> for example.

I'm using Visual Studio 2022 C++ 20.

Thank you for all the comments!

Here is the new code including all includes and the template comparison operator.

// String.cpp

#include <cassert>
#include <compare>
#include <stdexcept>

namespace mylib
{

    template <size_t N>
    struct String
    {
        String()
        {
            *data = '\0';
        }

        String(const char* str)
        {
            size_t l = strlen(str);

            if (l >= N)
                throw std::runtime_error(str);

            memcpy(data, str, l + 1);
        }

        template<size_t N2>
        auto operator<=>(const String<N2>& str)
        {
            return strcmp(data, str.data);
        }

        template<size_t N2>
        bool operator==(const String<N2>& str) const
        {
            return operator<=>(str) == 0;
        }

        char data[N];
    };

} // namespace mylib

int main()
{
    mylib::String<16> str1;
    mylib::String<16> str2("Hallo");
    mylib::String<162> str3 = "Hallo";
    mylib::String<16> str4 = "hallo";
    assert(str2 == str3);
    assert(str4 != str2);
    assert(str2 < str4);
    assert(str2 <= str4);
    assert(str4 > str2);
    assert(str4 >= str2);

    mylib::String<3> str5 = "Hallo";

    return 0;
}

Now im getting the following error 4 times:

1>E:\Projects\WindowsTest\String\String.cpp(53,2): error C2666: 'mylib::String<16>::operator <=>': overloaded functions have similar conversions
1>    E:\Projects\WindowsTest\String\String.cpp(29,8):
1>    could be 'auto mylib::String<16>::operator <=><16>(const mylib::String<16> &)' [rewritten expression '0 < (x <=> y)']
1>    E:\Projects\WindowsTest\String\String.cpp(29,8):
1>    or 'auto mylib::String<16>::operator <=><16>(const mylib::String<16> &)' [synthesized expression '(y <=> x) < 0']
1>    E:\Projects\WindowsTest\String\String.cpp(53,2):
1>    while trying to match the argument list '(mylib::String<16>, mylib::String<16>)'

What i am doing wrong?

Now i changed the data from char to array but still get the same errors as before.

// String.cpp

#include <array>
#include <cassert>
#include <compare>
#include <stdexcept>

namespace mylib
{

    // This class is for short constant strings on the stack without any overhead.
    template <size_t N>
    struct String
    {
        String()
        {
            *data = '\0';
        }

        String(const char* str)
        {
            size_t l = strlen(str);

            if (l >= N)
                throw std::runtime_error(str);

            memcpy(data, str, l + 1);
        }

        template<size_t N2>
        auto operator<=>(const String<N2>& str)
        {
            return data <=> str.data;
        }

        template<size_t N2>
        bool operator==(const String<N2>& str) const
        {
            return (data <=> str.data) == 0;
        }

        std::array<char, N> data;
    };

} // namespace mylib

int main()
{
    mylib::String<16> str1;
    mylib::String<16> str2("Hallo");
    mylib::String<32> str3 = "Hallo";
    mylib::String<32> str4 = "hallo";
    assert(str2 == str3);
    assert(str4 != str2);
    assert(str2 < str4);
    assert(str2 <= str4);
    assert(str4 > str2);
    assert(str4 >= str2);

    mylib::String<3> str5 = "Hallo";

    return 0;
}

Upvotes: 2

Views: 119

Answers (3)

Alan Birtles
Alan Birtles

Reputation: 36488

char data[N] has no comparison operator defined, if you use std::array<char, N> instead your code will work as it does have a comparison operator.

As a bonus as std::array is your only member you can just use a default operator:

#include <array>

namespace mylib
{
    template <size_t N>
    struct String
    {
        String()
        {
            data.fill('\0');
        }
    
        String(const char *str)
        {
            size_t l = strlen(str);
    
            if (l >= N)
                throw std::runtime_error(str);
    
            memcpy(data.data(), str, l + 1);
        }
    
        auto operator<=>(const String& str) const = default;
    
        std::array<char, N> data;
    };
}

Note that though there's no comparison operator defined for c-style arrays the default comparison operator is defined for array members so if using the default comparison operator you don't have to change to std::array (though I'd still recommend using std::array as it has other benefits over c-style arrays).

Upvotes: 2

Swift - Friday Pie
Swift - Friday Pie

Reputation: 14688

You can't compare arrays, that's completely wrong. Replace return data <=> str.data; with actual comparison algorithm. You may actually need separate != operation as a short-circuit.

Each instance of String<N> with unique N value is a unique type, so you have to have a template of operator:

    template <size_t S>
    auto operator<=>(const String<S>& str) const
    {
        //
    }

Another thing would be to have an ability to compare wita C-string. Which can't be done by this operator, because a pointer C-string doesn't carry size which could be used to construct String:

    auto operator<=>(const char* cstr) const
    {
        //
    }

And we may compare with array (or construct String from array):

    template <size_t S>
    auto operator<=>(const char (&array)[S]) const
    {
        //
    }

PS. You likely would need also a a constructor like that.Your string is also able to fail if source isn't null-terminated, strlen will have ndefined behaviour.

Upvotes: 0

Mrigayan
Mrigayan

Reputation: 80

When you overload the spaceship operator (<=>), it automatically provides default behavior for other relational operators (<, <=, >, >=) but not for operators (== and !=) by providing overloading for ==(it will also cover !=).

Also, spaceship operator does not work directly with arrays, it works with objects that have a defined spaceship operator.

This is updated code

#include <iostream>
#include <compare>
#include <string.h>
#include <cassert>
#include <string_view>

namespace mylib
{
    template <size_t N>
    struct String
    {
        String()
        {
            *data = '\0';
        }
    
        String(const char *str)
        {
            size_t l = strlen(str);
    
            if (l >= N)
                throw std::runtime_error(str);
    
            memcpy(data, str, l + 1);
        }
    
        auto operator<=>(const String& str) const
        {
            auto result = std::string_view(data) <=> std::string_view(str.data);;
            return result;
        }
        
        bool operator==(const String& str) const {
            return data == str.data;
        }
    
        char data[N];
    };
}

int main()
{
     mylib::String<16> str1;
        mylib::String<16> str2("Hallo");
        mylib::String<16> str3 = "Hallo";
        mylib::String<16> str4 = "hallo";
        assert(str2 == str3);
        assert(str4 != str2);
        assert(str2 < str4);
        assert(str2 <= str4);
        assert(str4 > str2);
        assert(str4 >= str2);
    
        mylib::String<3> str5 = "Hallo";

    return 0;
}

Upvotes: -2

Related Questions