Operator overloading based on passed int?

北慕城南 提交于 2019-12-24 05:54:32

问题


I am trying to implement my own programing language in C++.

When an error occours in my language (for example, during lexical analysis), an exception is thrown to be catched by the main function which then prints its message.

The exception can be of different types: either SyntaxError, or FunctionError and so on, all based on the Error class so that they can all be catched even being of different types.

Each sub class of errors anyway can generate different types of error, each one requiring different informations on what was happening when the error was thrown to construct its error message.

To solve this problem, I accounted for this solution:

//Error contains a public "message" attribute of type std::string, and a constructor to initialize that string

class SyntaxError: public Error {
    SyntaxError(int code, void* infos[]) {
        Error("SintaxError: ");
        switch (code) {
            //each sub error has its own code and based on that the function assumes the pointers to the objects needed to construct the error message are stored in the infos array
            case 1: //Unfinished string
            //it assumes the line at which the error is thrown is stored in the first position of the passed array
                message += "Unfinished string (at line " + std::to_string(*((int*) infos[0])) + ").";
            case 2: //Malformed number
                ...
            ... //other errors, each one assuming in the infos array are stored pointers to different types of objects
        }
    }
};

This however requires a great setup when you want to throw the error (because you must estabilish what type of error you want to throw and based on that create an array of pointers to the informations required) and it's also pretty messy in the constructor itself: I can't even name the objects required because I can't declare them in the switch statement and I should declare all of them at the start of the function.

So I tought to another solution: overloading the constructor with the objects needed, so that when I want to throw a unfinished string error I just have to pass an int, the line, and when I want to throw a malformed number error I have to pass an int and a string (the malformed number), and so on.

The point is, there will be errors requiring the same number, and types, of informations, like, a invalid escape sequence used in string error, for an example, would require an int (the line), and a string (the escape sequence used), just like the malformed number overload, and I don't know how to differentiate them.

So, is there a way to tell the compiler which function between different functions with the same parameter list to call based on an integer I pass to the function?


回答1:


Short answer

The technique you are looking for is named tag dispatching.


Long answer

Using ints (and std::integral_constants to dispatch the request?) is somehow hard to read, I'd rather define an enum class and use a template method.

As an example:

enum class Tag { FOO, BAR };

Then you have mainly two choices:

  • Template method and full specializations:

    template<Tag>
    void myMethod(int arg1, char arg2);
    
    template<>
    void myMethod<Tag::FOO>(int arg1, char arg2) {}
    
    template<>
    void myMethod<Tag::BAR>(int arg1, char arg2) {}
    

    To be invoked as:

    myMethod<Tag::FOO>(42, 'c');
    
  • Tag dispatching:

    void myMethod(tag<Tag::FOO>, int arg1, char arg2) {}
    
    void myMethod(tag<Tag::BAR>, int arg1, char arg2) {}
    

    To be invoked as:

    myMethod(tag<FOO>{}, 42, 'c');
    

    In this case, you have to define the tag struct somewhere. As an example:

    template<Tag>
    struct tag {};
    

Note: As mentioned in the comments, both the solutions work well also with a plain old enum.




回答2:


So how about going about this a different way?

During parsing/lexing/code generation etc keep a Context object (which could for example encapsulate the line number, column number and file name?). When you throw an exception, pass the Context object.

Further down the stack, catch and rethrow the exception with a nested exception (again created from the context at that point in the call stack).

Then when you print out the error message, do so by iterating through the nested exceptions.

example:

#include <stdexcept>
#include <exception>
#include <iostream>
#include <string>

struct context
{
    std::string file;
    int line;
};


struct context_exception : std::runtime_error
{
    context_exception(std::string message, const context& ctx)
    : std::runtime_error(message + " at line " + std::to_string(ctx.line) + " in file " + ctx.file)
    {}
};

struct syntax_error : context_exception
{
    syntax_error(const context& ctx)
    : context_exception("syntax error", ctx)
    {}
};

struct foo_error : context_exception
{
    foo_error(const context& ctx)
    : context_exception("foo error", ctx)
    {}
};


void parse_x(context my_context)
{
    my_context.line = 250;
    throw syntax_error(my_context);
}

void parse_y(context my_context)
{
    my_context.line = 200;
    try {
        parse_x(my_context);
    }
    catch(const std::exception& e)
    {
        std::throw_with_nested(foo_error(my_context));
    }
}

// trivial example of unwrapping an exception:

void print_exception(const std::exception& e, int level =  0)
{
    std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
    try {
        std::rethrow_if_nested(e);
    } catch(const std::exception& e) {
        print_exception(e, level+1);
    } catch(...) {}
}

int main()
{
    context ctx { "foo.cpp", 200 };

    try {
        parse_y(ctx);
    }
    catch(const std::exception& e)
    {
        print_exception(e);
    }   
}

expected output:

exception: foo error at line 200 in file foo.cpp
 exception: syntax error at line 250 in file foo.cpp


来源:https://stackoverflow.com/questions/39467246/operator-overloading-based-on-passed-int

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!