Reputation: 27
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
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
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
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