Home > front end >  Can I use C 20 concepts for partial template specialization?
Can I use C 20 concepts for partial template specialization?

Time:11-16

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

[Demo]

#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
  • Related