In general, a C++ program requires a main
function (at least- I believe- when running in a hosted environment). Presumably, there's just a main function that you aren't seeing that is using the code you wrote.
There are multiple ways this can be achieved.
- The C++ model allows for compiling multiple translation units (such as one containing your submission code, and another containing a
main
function that uses stuff defined in your submission code) and then linking their object code together to produce a program. If you want to learn more about the structure of C++ programs and compiling and linking, I suggest Cppcon's "Back to Basics" videos. Ex. Compiling and Linking and The Structure of a Program.
- C++ has its own textual inclusion/pasting features- namely,
#include
, which could be used to include a file containing your submission code "into" another file.
- Similar textual inclusion/pasting can be done by many other means without using
#include
.
- Building your thing into a library, and then building a separate executable that links with the library.
- ^This is probably not an exhaustive list. Ex. IIRC, HackerRank just uses IDE code folding features to fold out everything other than the submission code section.
The higher level idea is that leetcode gives you an interface it wants you to implement, and then it finds some way to depend on your implementation of that interface. At least in the case of C++, the names of the class and member function are parts of that interface.
Leetcode seems to take the third option. Julkar9 had a nice idea in their answer post to snoop around the filesystem of the execution environment. I just did essentially the same thing they did but for C++. The submission code is stashed to a file named "/mnt/prog.cpp"
, and the "full" code that the submission code is included into is stored in a file named "/mnt/prog_joined.cpp"
. I'm not sure whether this applies for all challenges/problems, or just for the one I did, and this may change in the future.
The code if you're interested:
#include <iostream>
#include <fstream>
#include <filesystem>
namespace fs = std::filesystem;
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::cout
<< "\nclang: v" << __clang_major__ << '.' << __clang_minor__ << '.' << __clang_patchlevel__
<< "\ncurrent_file_and_line: " << __FILE__ << ':' << __LINE__
<< "\npwd: " << fs::current_path()
;
// const std::array roots {"/mnt", "/leetcode"};
// for (const auto& root : roots) {
// for (const auto& dir_entry : fs::recursive_directory_iterator{root, fs::directory_options::skip_permission_denied}) {
// std::cout << '\n' << dir_entry;
// }
// }
const std::array files {
// "/mnt/compile.err",
"/mnt/prog.cpp", // just submission code
"/mnt/prog_joined.cpp", // submission code included into "wrapper" code
// "/mnt/prog" // the built program
// "/.dockerenv",
// "/mnt/data.1.in",
// "/mnt/judge.out",
// "/leetcode/data", ??
};
for (const auto& file_path : files) {
std::cout << "\n\n\ncontents of the file " << file_path;
std::ifstream file {file_path};
if (file.is_open()) { std::cout <<'\n'<< file.rdbuf(); }
else { std::cout << "\nfailed to print file " << file_path; }
}
return {};
}
};
Which showed that the wrapper (for the problem I ran the above code "on"/in) is like this (at the time that I ran it):
#include "precompiled/headers.h"
using namespace std;
// user submitted code insert below
_Deserializer_ _des_;
_Serializer_ _ser_;
// <contents of prog.cpp>
class __DriverSolution__ {
public:
vector<int> __helper__(
vector<int>& param_1, int param_2) {
vector<int> ret = Solution().twoSum(param_1, param_2); return ret;
}
};
int main(int argc, char *argv[]) {
char SEPARATOR[] = "\x1b" "\x09" "\x1d";
setbuf(stdout, NULL);
ofstream fout("user.out");
string line;
while (getline(cin, line)) {
vector<int> param_1 = _des_.deserialize<vector<int>>(line);
getline(cin, line);
int param_2 = _des_.deserialize<int>(line);
vector<int> ret = __DriverSolution__().__helper__(
param_1, param_2
);
string out = _ser_.serialize(ret);
fout << out << endl;
cout << SEPARATOR;
}
return 0;
}
#pragma GCC optimize ("O2")
Side commentary: Leetcode's usage of identifiers containing __
is not amazing. I'm pretty sure identifiers like that are reserved for implementations, and that programs that do that are ill-formed-NDR (see cppref)- at least on paper.
As for how exactly this non-#include
text inclusion is done, I tried looking to see if there was some script in the execution environment doing it by grepping for prog_joined
in the execution environment, but had difficulty due to execution timeouts, and didn't find anything in the places I'd expect it to be if it existed. I don't know a lot of Docker, but I'm going to guess that /mnt
is a bind mount to a host system or something, and that the host system contains the stuff that does that, or that communicates with some other system that does that. This is speculation, but at this point, there's not much of practical nature to gain from knowing the answer to this final piece of the puzzle.
On the practical applications of what we do know, Julkar9 mentioned that leetcode apparently doesn't let you define your own main to prevent you from optimizing it. I don't know if that claim of rationale is true or not, but fun fact- now that you know how this works, you can mess with it:
class Solution {
public:
int foo() {/*...*/}
};
int main() {
// whatever the heck you want. Ex. optimize input parsing
return 0;
}
#define main I_REJECT_YOUR_ENTRYPOINT_AND_SUBSTITUTE_MY_OWN
^That's something you couldn't do in the confines of leetcode if leetcode was building through separate compilation and link. If you weren't limited by leetcode's interface, there are plenty of other things you could do, such as probably some sort of tinkering with linker scripts (but I don't know. I don't write linker scripts).