skip to Main Content

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

namespace mylib
{
    template <size_t N>
    struct String
    {
        String()
        {
            *data = '';
        }
    
        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:ProjectsWindowsindIndDlg.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 = '';
        }

        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:ProjectsWindowsTestStringString.cpp(53,2): error C2666: ‘mylib::String<16>::operator <=>’: overloaded functions have similar conversions
1> E:ProjectsWindowsTestStringString.cpp(29,8):
1> could be ‘auto mylib::String<16>::operator <=><16>(const mylib::String<16> &)’ [rewritten expression ‘0 < (x <=> y)’] 1> E:ProjectsWindowsTestStringString.cpp(29,8):
1> or ‘auto mylib::String<16>::operator <=><16>(const mylib::String<16> &)’ [synthesized expression ‘(y <=> x) < 0’] 1> E:ProjectsWindowsTestStringString.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 = '';
        }

        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;
}

3

Answers


  1. 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.

    Login or Signup to reply.
  2. 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('');
            }
        
            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).

    Login or Signup to reply.
  3. 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 = '';
            }
        
            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;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search