Medical physicist
Medical physicist

Reputation: 2594

Custom C++ exceptions with template arguments

I'm trying to learn how to use custom C++ exceptions with template arguments. This is a dummy program that I'm trying to compile unsuccessfully:

#include <string>
#include <iostream>

template <class T>
class MyException : public std::exception {
    public:
        T error;
        MyException(T err) { error = err; };
};


int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (MyException e) {
        std::cout << e << std::endl;
        return 1;
    };
}

This is the error I get:

<source>: In function 'int main()':
<source>:18:9: error: invalid use of template-name 'MyException' without an argument list
   18 |  catch (MyException e) {
      |         ^~~~~~~~~~~
<source>:18:9: note: class template argument deduction is only available with '-std=c++17' or '-std=gnu++17'
<source>:5:7: note: 'template<class T> class MyException' declared here
    5 | class MyException : public std::exception {
      |       ^~~~~~~~~~~
<source>:19:22: error: 'e' was not declared in this scope
   19 |         std::cout << e << std::endl;
      |                      ^

Could you please help me to fix it for C++11?

Upvotes: 3

Views: 988

Answers (5)

rustyx
rustyx

Reputation: 85371

Instead of answering directly, I will attempt to address the underlying confusion about C++ templates to hopefully prevent more of these issues from arising down the road.

Roughly speaking, the syntax template <...> class MyException ... {}; does NOT introduce anything "tangible" (a type or a value) into the program. There is no type MyException. The compiler instantiates a class from the template each time it encounters a specific syntax MyException<...>. But each instance of MyException<...> is unique for each combination of the template arguments; MyException<A> is distinct from MyException<B>, they might as well be called Bla and Pft - there is nothing in common between them.

So how to make MyException<A> and MyException<B> act in a common way? Well, just like you'd do with two unrelated classes - use inheritance and polymorphism.

// define some common base that is NOT a template
class MyException : public std::exception {
    public:
        // put any common API's here ...
        virtual std::string commonWork() = 0;
        virtual ~MyException() {}
};

// now make each template inherit from the common base ...
template <class T>
class MyExceptionImpl : public MyException {
    public:
        T error;
        MyExceptionImpl(T err) { error = err; }
        std::string commonWork() override { return ""; }
};

Now you can catch (MyException const& e) { ... }.

Upvotes: 2

Aleon
Aleon

Reputation: 307

This answer is really just piecing together the things already mentioned by @Marek R and @churill.

In the comments you say you

  1. do not want to catch std::exception, or use std::exception::what().
  2. want to print the error field specifically.

This is kind of just re-implementing the what functionality using a custom virtual function, but you could do it like this:

#include <string>
#include <iostream>

class MyBaseException : public std::exception {
    public:
        virtual std::ostream& intoStream(std::ostream& stream) const = 0;
};

std::ostream& operator<<(std::ostream& stream, const MyBaseException& e) {
    return e.intoStream(stream);
}

template <typename T>
class MyException : public MyBaseException {
    public:
        T error;
        MyException(T err) { error = err; };
    
        std::ostream& intoStream(std::ostream& stream) const override {
            return stream << error;
        }
};

int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (const MyBaseException& e) {
        std::cout << e << std::endl;
        return 1;
    };
}

Since only class methods can be virtual, if you want to use the << operator, you need a separate virtual function, that can access the error field.

Upvotes: 0

The Philomath
The Philomath

Reputation: 992

@Marek has already explained that we can't catch template in C++. For your custom messages you can print them like:

#include <string>
#include <iostream>

template <class T>
class MyException : public std::exception {
    public:
        T error;
        MyException(T err) { error = err; };
        template<typename U>
        friend std::ostream& operator <<(std::ostream & , const MyException<U>&);
};

template <typename T>
std::ostream& operator<<( std::ostream& o, const MyException<T>& e) {
   return o<<e.error;
}

int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (const MyException<std::string>& e) {
        std::cout << e << std::endl;
        return 1;
    };
}

Upvotes: 0

Lukas-T
Lukas-T

Reputation: 11340

c++ doesn't have a "template catch", you can only catch actual classes like

catch (MyException<string> e) { ... }

Luckily std::exception::what() is virtual, so you could catch std::exception by const reference and print the result of what().

catch (std::exception const &e) {
    std::cout << e.what() << std::endl;
    return 1;
};

And override what() in MyException to create the right error message there:

template <class T>
class MyException : public std::exception {
    public:
        T error;
        MyException(T err) { error = err; };
       
        const char* what() const noexcept override {
            // apply logic to create error message
        }
};

This way you can catch anything that inherits from std::exception.

Upvotes: 2

Marek R
Marek R

Reputation: 37900

You can't catch any template! You can catch only specific type so only specific instance of template. So your code should look like this (quick fix):

#include <string>
#include <iostream>

template <class T>
class MyException : public std::exception {
    public:
        T error;
        MyException(T err) { error = err; };
};


int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (const MyException<std::string>& e) {
        std::cout << e.what() << std::endl;
        return 1;
    };
}

https://godbolt.org/z/P4czdP

If you need some way to catch all exceptions for this template, you need extra layer of inheritance to introduce common unique parent type for this template:

#include <string>
#include <iostream>


class MyCommonException : public std::exception
{};

template <class T>
class MyException : public MyCommonException {
    public:
        T error;
        MyException(T err) { error = err; };
};


int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (const MyCommonException& e) {
        std::cout << e.what() << std::endl;
        return 1;
    };
}

https://godbolt.org/z/xz8r5a

Upvotes: 4

Related Questions