problem_format: add C++ templates#158
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
sample_files/problem_setting/test/testsuite/esoteric_validator/cases/noeol/input
Show resolved
Hide resolved
62f93d3 to
195ee98
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.
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.
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.
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.
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.
Then, just declare lo and hi as T to avoid pointlessly adding limitations.
There was a problem hiding this comment.
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.
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.
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.
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.
| 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.
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.
Sounds like this should be an enum.
There was a problem hiding this comment.
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.
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.
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.
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.
Can we write this without the repeating c = getchar()?
There was a problem hiding this comment.
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.
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.
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.
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.
Actually wait... why are you using the POSIX regex library instead of the C++11 one?
There was a problem hiding this comment.
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.
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.
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.
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.
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.