Sign in to follow this  

[C++] Templates and optional arguments

This topic is 3549 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

So, I just dived into templates :), and wanted to write my own error handling function, which is as follows:
template <class Argument>
void error(int level, const char *error, Argument argument = 0, char *file = __FILE__, int line = __LINE__)
{
    switch (level)
    {
        default:
        case FATAL:
        {
            std::cout << "Fatal error: ";
            exit(1);
            break;
        }
        case WARNING:
        {
            std::cout << "Warning: ";
            break;
        }
    }
    
    if (argument)
    {
         std::cout << "\"" << argument << "\" ";
    }

    std::cout << error << " (" << file << "." << line << ");\n";
}

Well, I can't get it fully to work, but you can get an impression of what I try to do: I want 'argument' to be optional, if it is not set it should also not be outputted. How can I do this properly? Thanks, Decrius

Share this post


Link to post
Share on other sites
You'd either have to create an overload without the Argument type and physical parameter, or call the template function with a dummy type:


error<int>(FATAL, "rah!");




But it might be better to structure the code so that you can use streams more directly. Something like:


FATAL << "this is a fatal error";
WARNING << "this is only a warning";




FATAL, WARNING and friends could be macros:


error_stream << "fatal error in " << __FILE__ << " on line " << __LINE__ << ": "




This probably isn't the best way and there are numerous others, but they don't restrict you to one argument.

Share this post


Link to post
Share on other sites
In the current language, function template parameters can't be deduced from default arguments; neither can function template parameters have default values, unlike class template parameters. This is an inconvenience that will be fixed in the next standard, but in the meantime, an acceptable workaround is to write an overload that forwards to the "full" version of the template.

Anyway, your use of __FILE__ and __LINE__ is problematic: because they are expanded by the preprocessor, they will always expand to the same values — specifically, the file and line where error is declared! If you want to output the file and line of the call site, you'll have to resort to a macro:
#define ERROR(lvl, err, arg) error(lvl, err, arg, __FILE__, __LINE__)
Unfortunately, macros can't have default arguments, neither can they be overloaded...

Also, error/warning/log messages should probably go to std::cerr or std::clog. They both write to standard error instead of standard output (so that, for instance, error messages can be redirected to a file independent of normal output); additionally, std::cerr does unbuffered output so that everything is always written even if the program crashes.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sharlin
In the current language, function template parameters can't be deduced from default arguments; neither can function template parameters have default values, unlike class template parameters. This is an inconvenience that will be fixed in the next standard, but in the meantime, an acceptable workaround is to write an overload that forwards to the "full" version of the template.

Anyway, your use of __FILE__ and __LINE__ is problematic: because they are expanded by the preprocessor, they will always expand to the same values — specifically, the file and line where error is declared! If you want to output the file and line of the call site, you'll have to resort to a macro:
#define ERROR(lvl, err, arg) error(lvl, err, arg, __FILE__, __LINE__)
Unfortunately, macros can't have default arguments, neither can they be overloaded...

Also, error/warning/log messages should probably go to std::cerr or std::clog. They both write to standard error instead of standard output (so that, for instance, error messages can be redirected to a file independent of normal output); additionally, std::cerr does unbuffered output so that everything is always written even if the program crashes.


Ah thanks, clog and cerr output to the same file though.

But I'd like to use a function for it, since I'd like to output "Fatal error: " too, and don't want to rewrite that everywhere I use it.

The insertion operator looks usable, but I can't find anywhere on writting my own << operator function.

Something like:

fatal_error << "could not load texture: " << texture_name;
fatal_error << "too many elements: " << number_of_elements;

And that that function would do something like this:

fatal_error& operator<< (...)
{
std::cerr << "Fatal error: " << input << " file: " << file << " line: " << line;
}

Is that possible? How would you do that? (I'm new to costum operators aswell...)

Share this post


Link to post
Share on other sites
Quote:
Original post by Decrius
Ah thanks, clog and cerr output to the same file though.


You can customize where they write by creating your own stream buffer. But I would make the logging destination customisable and only use std::cerr/clog as default desintations.

Quote:
But I'd like to use a function for it, since I'd like to output "Fatal error: " too, and don't want to rewrite that everywhere I use it.


You can of course do this with a stream too. One way would be to have the stream buffer print "fatal error: " at the start of each line. Another solution would be to have something like:


// in a header file
class error_init
{
public:
error_init(const std::string &prefix, std::ostream &out) :
prefix_(prefix),
out_(out)
{
}

template<typename T>
std::ostream &operator<< (const T &x)
{
return out_ << prefix_;
}

private:
std::string prefix_;
std::ostream &out_;
};

extern error_init fatal;
extern error_init warning;





// in an implementation file

error_init fatal("Fatal error: ", std::cerr);
error_init warning("Warning: ", std::clog);




Now you can do:


warning << "something a bit strange happened!\n";




You'll of course need a little bit of macro trickery again to include the file and line references.


Quote:
The insertion operator looks usable, but I can't find anywhere on writting my own << operator function.

Something like:

fatal_error << "could not load texture: " << texture_name;
fatal_error << "too many elements: " << number_of_elements;

And that that function would do something like this:

fatal_error& operator<< (...)
{
std::cerr << "Fatal error: " << input << " file: " << file << " line: " << line;
}

Is that possible? How would you do that? (I'm new to costum operators aswell...)


If your logging stream objects are already std::ostreams (like std::cerr and std::clog are), you shouldn't need to do anything, unless you want to stream out objects of custom type.

Share this post


Link to post
Share on other sites
To be clear...

void error(int level, const char *error, Argument argument = 0, char *file = __FILE__, int line = __LINE__)


The __FILE__ and __LINE__ in the above function are evaluated at the point that the error function is declared, not where it is called. :)


struct err_data {
bool fatal;
std::string prefix;
std::ostream* out;
};

class err_stream {
err_data data;
const char* file;
int line;
err_stream(err_data& data_, const char* file_, int line_):
data(data_), file(file_), line(line_)
{}
template&lt;typename T&gt;
std::ostream &operator&lt;&lt; (const T &x)
{
if (fatal) {
*data.out &lt;&lt; data.prefix &lt;&lt; x &lt;&lt; "\n";
exit(1);
}
return *data.out &lt;&lt; file &lt;&lt; "(" &lt;&lt; line &lt;&lt; ") " &lt;&lt; data.prefix &lt;&lt; x &lt;&lt; "\n";
}
};

class err_manager {
err_data data;
err_stream operator()(const char* file, int line) {
return err_stream(data, file, line);
}
err_manager(const std::string &prefix, std::ostream &out, bool fatal=false) {
data.prefix = prefix;
data.out = &out;
data.fatal = fatal;
}
};

err_manager fatal("Fatal error: ", std::cerr, true);
#define FATAL fatal(__FILE__, __LINE__)
err_manager warning("Warning: ", std::clog);
#define WARNING warning(__FILE__, __LINE__)

void test() {
WARNING &lt;&lt; "Did the warning work?";
FATAL &lt;&lt; "This is a fatal error!";
}



Here we have a few tricks.

The manager, under operator(), produces a stream object.
The stream object gets the file/line numbers on construction, and accepts operator << streaming.
The macros call the manager's operator()s with the current file/line names.

Share this post


Link to post
Share on other sites

This topic is 3549 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this