I was given an assignment on my computer science class to implement a String<T>
class in C which would have print
method throw an exception, unless T = char
. So I defined a method for template<typename T> class String
this way:
void print(std::basic_ostream<T> stream) {
throw StringTypeError("Can't print with these types");
}
And defined a specialization like so:
template<>
void String<char>::print(std::basic_ostream<char> stream) {
for(unsigned long i = 0; i < size; i )
stream << charAt(i);
}
That totally works, however I've recently found out about concepts and decided to allow usage of print()
for any type which can be <<
/>>
into IO streams. I created a small concept:
template<typename T>
concept IO = requires(T a) {
std::cout << a;
std::cin >> a;
};
And tried to alter my specialization this way:
template<IO T>
void String<T>::print(std::basic_ostream<T> stream) {
for(unsigned long i = 0; i < size; i )
stream << charAt(i);
}
But the compiler rejected with "Type constraint differs in template redeclaration" error.
Am I making a mistake which can be corrected or is it impossible to use concepts in this way? (if that's so, is there any concept-like alternatives?)
CodePudding user response:
is there any concept-like alternatives?
There is an appropriate tool for the job, a particularly nice new feature with concepts and its related requires-clauses, one which is not possible with pre-C 20 SFINAE, namely that non-template member functions of class templates can be declared with requires-clauses. This means that you can define both of your mutually exclusive print
member function in the primary template:
template<typename T>
class String {
void print(std::basic_ostream<T>&) requires (!IO<T>) {
throw StringTypeError("Can't print with these types");
}
void print(std::basic_ostream<T>& stream) requires (IO<T>) {
for(unsigned long i = 0; i < size; i )
stream << charAt(i);
}
// ...
};
CodePudding user response:
You can keep both functions, the one using the concept, and the specialization.
You don't need to change the char
specialization after introducing the concept, since char
is a type that fulfills that IO
requirement (you can cout
and cin
characters).
#include <fmt/core.h>
#include <iostream>
#include <stdexcept>
#include <vector>
struct StringTypeError : public std::runtime_error {
StringTypeError(std::string_view message)
: std::runtime_error{ message.data() }
{}
};
template <typename T>
concept IO = requires(T a) {
std::cout << a;
std::cin >> a;
};
template <typename T>
class String {
public:
String(std::vector<T> data) : data_{ std::move(data) } {}
size_t size() { return data_.size(); }
T& charAt(size_t pos) { return data_.at(pos); }
void print(std::basic_ostream<T>& stream);
private:
std::vector<T> data_{};
};
template <IO U>
void String<U>::print(std::basic_ostream<U>&) {
throw StringTypeError("Can't print with these types");
}
template <>
void String<char>::print(std::basic_ostream<char>& stream) {
for(unsigned long i = 0; i < size(); i )
stream << charAt(i);
}
int main() {
try {
String<wchar_t> str_wc{ { L'a', L'b', L'c' }};
str_wc.print(std::wcout);
} catch (const std::exception& e) {
fmt::print("Error: {}\n", e.what());
}
String<char> str_c{ { 'a', 'b', 'c' }};
str_c.print(std::cout);
}
// Outputs:
//
// Error: Can't print with these types
// abc