Skip to content

Conversation

@Riolku
Copy link
Contributor

@Riolku Riolku commented Jan 8, 2024

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.

@quantum5
Copy link
Member

quantum5 commented Jan 8, 2024

Can we make sure the templates pass clang-format?

@Riolku
Copy link
Contributor Author

Riolku commented Jan 8, 2024

Yes, I'd be happy to add CI for that. Would you also like me to add some tests for the methods using ctest?

@quantum5
Copy link
Member

quantum5 commented Jan 8, 2024

Would you also like me to add some tests for the methods using ctest?

Sure, but at least make sure they build or something.

@Riolku Riolku force-pushed the cpp-templates branch 2 times, most recently from 03b9a4b to 76ec663 Compare January 9, 2024 16:01
@Riolku Riolku force-pushed the cpp-templates branch 10 times, most recently from 24cee83 to efffd68 Compare September 7, 2024 21:20
@kiritofeng kiritofeng requested a review from Xyene September 8, 2024 14:56
Copy link
Member

@kiritofeng kiritofeng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise LGTM

@Riolku Riolku force-pushed the cpp-templates branch 3 times, most recently from 62f93d3 to 195ee98 Compare September 9, 2024 01:16
Copy link
Member

@kiritofeng kiritofeng left a 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) {
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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...

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

@kiritofeng kiritofeng left a 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) {
Copy link
Member

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...

Comment on lines 11 to 17
namespace CheckerCodes {
int AC = 0;
int WA = 1;
int PE = 2;
int PARTIAL = 7;
} // namespace CheckerCodes
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Contributor Author

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(?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to constexpr

Comment on lines 35 to 49
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();
}
Copy link
Member

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()?

Copy link
Contributor Author

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.

Comment on lines 11 to 17
namespace CheckerCodes {
int AC = 0;
int WA = 1;
int PE = 2;
int PARTIAL = 7;
} // namespace CheckerCodes
Copy link
Member

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) {
Copy link
Member

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.

@Riolku Riolku force-pushed the cpp-templates branch 2 times, most recently from 160629b to 8b9ac02 Compare December 23, 2024 19:08
return parsedInt;
}
template <typename T>
std::vector<T> readIntArray(size_t N, long long lo, long long hi) {
Copy link
Member

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>
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.
Copy link
Member

@quantum5 quantum5 left a 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) {
Copy link
Member

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>
Copy link
Member

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.

@kiritofeng kiritofeng merged commit 549d173 into DMOJ:master Dec 24, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants