-
Notifications
You must be signed in to change notification settings - Fork 90
problem_format: add C++ templates #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Can we make sure the templates pass |
|
Yes, I'd be happy to add CI for that. Would you also like me to add some tests for the methods using |
Sure, but at least make sure they build or something. |
03b9a4b to
76ec663
Compare
24cee83 to
efffd68
Compare
kiritofeng
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise LGTM
sample_files/problem_setting/test/testsuite/esoteric_validator/cases/noeol/input
Show resolved
Hide resolved
62f93d3 to
195ee98
Compare
kiritofeng
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly LGTM
| return parsedInt; | ||
| } | ||
| template <typename T> | ||
| std::vector<T> readIntArray(size_t N, long long lo, long long hi) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably add as a comment that this will need modification if you want to read unsigned long long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to docs. I think fixing this function to handle uint64 is a pain... The best option that comes to mind is having readInt have a template parameter for the return type. Inferring template parameters from the arguments, like doing readInt(T lo, T hi) doesn't work because the compiler shits itself if you try readInt(0, 1e5) or readInt(1, 4U) because the types are different.
Hard-requiring uint64 is rare, so I think this limitation is ok, since the alternatives sacrifice ergonomics greatly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you need to specify a template type anyway since T doesn't appear in the argument list at all? So the problem you are trying to address with type inference doesn't actually exist...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, readIntArray forces specifying the template parameter and this is intentional - it's impossible to ergonomically infer it from the arguments, and having the return value always be long longs is not great, unlike with readInt where immediate downcasting is possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then, just declare lo and hi as T to avoid pointlessly adding limitations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reasons discussed before this isn't possible. Specifically, readIntArray(N, 1, 1e9) no longer works in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readIntArray(N, 1, 1e9) never works because you always need to specify a return type due to the template being on the return value only. So you are optimizing for a case that doesn't exist.
If anything, using T in the parameter list will allow readIntArray(N, 1, 100); to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, so to make readIntArray(N, 1, 1e9) work, we force passing the template parameter. Then readIntArray<int>(N, 1, 1e9) works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proper way to do this in C++ if you care about type deduction would be something like:
template<typename T>
T readInteger(T lo, T hi) {
// ...
}
constexpr auto readInt = &readInteger<int>;
// more aliases as needed
template<typename T>
std::vector<T> readIntArray(size_t N, T lo, T hi) {
// ...
}You know what, this is always going to be terrible and I am done trying to police this into something sane.
kiritofeng
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
| return parsedInt; | ||
| } | ||
| template <typename T> | ||
| std::vector<T> readIntArray(size_t N, long long lo, long long hi) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you need to specify a template type anyway since T doesn't appear in the argument list at all? So the problem you are trying to address with type inference doesn't actually exist...
| namespace CheckerCodes { | ||
| int AC = 0; | ||
| int WA = 1; | ||
| int PE = 2; | ||
| int PARTIAL = 7; | ||
| } // namespace CheckerCodes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like this should be an enum.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use a bare enum, then AC, WA, PE, PARTIAL leak into the symbol table, and if we use an enum class, then std::exit(CheckerCodes::AC) doesn't work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't want the enum to leak into the global scope, then declare it in a namespace. Don't declare a bunch of random mutable global variables. You can actually do CheckerCodes::AC = 1337; like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like it's best to just use constexpr and be done with it, no?
I dont see what you mean with CheckerCodes::AC = 1337, TMK that requires a previous declaration of the variable(?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to constexpr
| int c = getchar(); | ||
| ConsumeResult result = NO_WHITESPACE; | ||
| while (isspace(c) && c != EOF) { | ||
| if (result == NO_WHITESPACE) { | ||
| result = NO_LINES; | ||
| } | ||
| if (c == '\r' || c == '\n') { | ||
| result = LINES; | ||
| } | ||
| c = getchar(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we write this without the repeating c = getchar()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how. Would probably require adding break and continue or something, which is in my opinion worse.
195ee98 to
ef14302
Compare
| namespace CheckerCodes { | ||
| int AC = 0; | ||
| int WA = 1; | ||
| int PE = 2; | ||
| int PARTIAL = 7; | ||
| } // namespace CheckerCodes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't want the enum to leak into the global scope, then declare it in a namespace. Don't declare a bunch of random mutable global variables. You can actually do CheckerCodes::AC = 1337; like this.
| return parsedInt; | ||
| } | ||
| template <typename T> | ||
| std::vector<T> readIntArray(size_t N, long long lo, long long hi) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then, just declare lo and hi as T to avoid pointlessly adding limitations.
160629b to
8b9ac02
Compare
| return parsedInt; | ||
| } | ||
| template <typename T> | ||
| std::vector<T> readIntArray(size_t N, long long lo, long long hi) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readIntArray(N, 1, 1e9) never works because you always need to specify a return type due to the template being on the return value only. So you are optimizing for a case that doesn't exist.
If anything, using T in the parameter list will allow readIntArray(N, 1, 100); to work.
| #include <cstdlib> | ||
| #include <fstream> | ||
| #include <iostream> | ||
| #include <regex.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually wait... why are you using the POSIX regex library instead of the C++11 one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The C++11 library takes 5 seconds to compile on DMOJ, which is unacceptably slow for a checker/interactor. The C library compiles instantly, since it's available as a shared library instead of a template header library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay fine. I don't like the POSIX library, but C++ sucks.
Many templates have been floating around in the DMOJ community for validation and input handling in checkers. This commit aims to consolidate them. It has two main goals: - Correct. Duh. - Simple. Other templates that circulate, including the ones I have published, are too complex. People naively try and write their own. I am sick and tired of reading over incorrect validators. These templates forgo some principles of good design (such as object-oriented programming) in favour of pure simplicity. They should be simple enough that they are understandable by the broader community, and are not a black box. Hopefully this also dissuades re-writing.
8b9ac02 to
f796f19
Compare
quantum5
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to deal with this anymore.
| return parsedInt; | ||
| } | ||
| template <typename T> | ||
| std::vector<T> readIntArray(size_t N, long long lo, long long hi) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proper way to do this in C++ if you care about type deduction would be something like:
template<typename T>
T readInteger(T lo, T hi) {
// ...
}
constexpr auto readInt = &readInteger<int>;
// more aliases as needed
template<typename T>
std::vector<T> readIntArray(size_t N, T lo, T hi) {
// ...
}You know what, this is always going to be terrible and I am done trying to police this into something sane.
| #include <cstdlib> | ||
| #include <fstream> | ||
| #include <iostream> | ||
| #include <regex.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay fine. I don't like the POSIX library, but C++ sucks.
Many templates have been floating around in the DMOJ community for validation and input handling in checkers. This commit aims to consolidate them. It has two main goals:
Correct. Duh.
Simple. Other templates that circulate, including the ones I have published, are too complex. People naively try and write their own. I am sick and tired of reading over incorrect validators.
These templates forgo some principles of good design (such as object-oriented programming) in favour of pure simplicity. They should be simple enough that they are understandable by the broader community, and are not a black box. Hopefully this also dissuades re-writing.