This is essentially the same argument as "one can write secure software in C++". If it's not enforced by the language and tooling, one can't safely assume anything.
You can write an incorrect program in any language. Some kind of self-discipline is important and necessary.
Try: "g++ -std=c++20 lb.cc && ./a.out iodhuamplrnc words".
#include <array>
#include <bit> // popcount
#include <iostream>
#include <utility>
#include <string_view>
#include <vector>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct Word {
std::string_view str;
unsigned bits{};
static unsigned index(unsigned c) { return c - 'a'; }; // 'a'..'z' -> 0..25; others -> >25
Word(std::string_view s, unsigned accept, long long sides) : str(s) {
long long prev_side{-1}, side; // Values are 0..3. First letter gets a free pass.
for (char c: s) {
const auto ix = index(c);
if (ix < 26 && (accept & (1u << ix)) && (side = ((sides >> 2*ix) & 0x3)) != prev_side) {
bits |= (1u << ix), prev_side = side;
} else { str = {}; return; }}}
};
int main(int ac, char** av) {
auto usage = [av](){ std::cerr << "usage: " << av[0] << " abcdefghijkl [<wordlist>]\n"; };
if (!(ac == 2 || ac == 3)) { return usage(), 1; }
const std::array prefixes = { "", "/usr/share/dict/", "/usr/share/dict/american-english-" };
const auto name = (ac == 3) ? av[2] : prefixes.back() + std::string{"large"};
std::string_view file;
for (auto pfx = 0u; pfx != prefixes.size(); ++pfx) {
int fd = ::open((prefixes[pfx] + name).c_str(), O_RDONLY);
std::size_t file_size = ::lseek(fd, 0, SEEK_END);
void const* addr = ::mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0);
if (addr != MAP_FAILED) { file = {static_cast<char const*>(addr), file_size}; break; }
}
if (file.empty()) { return std::cerr << av[0] << ": cannot read " << name << '\n', 3; }
auto target_sum = 0u; auto sides_sum = 0ll;
for (unsigned i{}, ix{}; i < 12 && (ix = Word::index(av[1][i])) < 26; ++i)
{ target_sum |= (1u << ix), sides_sum |= (i/3ll << 2*ix); }
if (std::popcount(target_sum) != 12 || av[1][12] != '\0') { return usage(), 3; }
const auto target = target_sum; const auto sides = sides_sum;
const auto candidates = [target,sides,file] { std::array<std::vector<Word>, 26> candidates;
for (std::string_view in = file, next; !(next = in.substr(0, in.find('\n'))).empty();
in.remove_prefix(next.size() + (next.size() < in.size()))) { // Skip past '\n' if present
if (const Word word{next, target, sides}; word.str.size() >= 3)
{ candidates.at(Word::index(word.str.front())).push_back(word); }
}
return candidates;
}();
std::array<std::vector<std::array<std::string_view, 2>>, 100> answers; // radix sort by length
for (auto const& firsts: candidates) for (const auto first: firsts) {
for (const auto second: candidates.at(Word::index(first.str.back()))) {
if ((first.bits | second.bits) == target) {
answers.at(first.str.size() + second.str.size()).push_back({first.str, second.str}); }}
}
for (auto const& of_len_N: answers) for (const auto answer: of_len_N)
{ std::cout << answer[0] << ' ' << answer[1] << '\n'; }
}std::string_view lets you have some of the bugs that are caused by misusing pointers.
I have seen them used in the PIMPL idiom, which is a rather controlled case.