Info
-
Did you know about the proposal to make
printf
compile-time safe and with named arguments?
Example
int main() {
fmt::print(FMT_STRING("{}"), 42);
fmt::print(FMT_STRING("{}, {}"), 42); // Compilation Error
}
Puzzle
- Can you implement a subroutine
has_valid_args
which verifies that named arguments match the format?
constexpr auto has_valid_args(auto fmt, auto... args) {
return /*TODO*/ false;
}
static_assert(not has_valid_args(""_fmt, "arg1"_arg = 42));
static_assert(not has_valid_args(""_fmt, "arg1"_arg = 42, "arg2"_arg = "Quantlab"));
static_assert(not has_valid_args("{}"_fmt, "arg1"_arg = 42));
static_assert(not has_valid_args("{arg2}"_fmt, "arg1"_arg = 42));
static_assert(not has_valid_args("{arg1}"_fmt, "arg1"_arg = 42, "arg2"_arg = "Quantlab"));
static_assert(not has_valid_args("{arg1}"_fmt, "arg1"_arg = 42, "ARG2"_arg = "Quantlab"));
static_assert(has_valid_args("{arg1}, {arg2}"_fmt, "arg1"_arg = 42, "arg2"_arg = "Quantlab"));
static_assert(has_valid_args("{arg2}, {arg1}"_fmt, "arg1"_arg = 42, "arg2"_arg = "Quantlab"));
static_assert(has_valid_args("{arg1}, {arg2}"_fmt, "arg2"_arg = "Quantlab", "arg1"_arg = 42));
static_assert(has_valid_args("{arg2}, {arg1}"_fmt, "arg2"_arg = "Quantlab", "arg1"_arg = 42));
Solutions
constexpr std::string_view operator "" _fmt(char const * str, std::size_t n)
{
return std::string_view{str, n};
}
struct arg_type
{
template <typename T>
constexpr arg_type operator=(T const & val)
{
return *this;
}
std::string_view name;
};
constexpr arg_type operator "" _arg(char const * str, std::size_t n)
{
return arg_type{ { str, n } };
}
constexpr auto has_valid_args(auto fmt, auto... args)
{
auto begin = std::begin(fmt);
auto end = std::end(fmt);
int named_args_count = 0;
bool result = true;
auto p = begin;
while(p != end) {
auto c = *p++;
if(c == '{') {
auto c = *p;
if(c != '}') {
auto it = p;
do {
++it;
c = *it;
}
while(it != end && (( 'a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')));
// p -> it = fmt_arg_name
++named_args_count;
auto fmt_arg_name = std::string_view{ p, it };
result = result && ((fmt_arg_name == args.name) || ... );
p = it;
}
++p;
}
}
return sizeof...(args) == named_args_count && result;
}
constexpr auto contains_arg(const auto& fmt, const auto& arg_name) {
auto it = std::cbegin(fmt);
while (it != std::cend(fmt)) {
it = std::search(it, std::cend(fmt),
std::cbegin(arg_name), std::cend(arg_name));
if (it == std::cend(fmt)) {
break;
}
const auto close_brace_it = std::next(it, std::size(arg_name));
if (it != std::cbegin(fmt) and close_brace_it != std::cend(fmt)
and *std::prev(it) == '{' and *close_brace_it == '}') {
return true;
}
it = close_brace_it;
}
return false;
}
constexpr auto operator""_fmt(const char* ptr, std::size_t sz) {
return std::string_view{ptr, ptr + sz};
}
struct arg {
constexpr auto& operator=(const auto&) { return *this; }
std::string_view name;
};
constexpr auto operator""_arg(const char* ptr, std::size_t sz) {
return arg{.name = {ptr, sz}};
}
constexpr auto has_valid_args(auto fmt, auto... args) {
return (contains_arg(fmt, args.name) and ...);
}
struct udl_arg {
const std::string_view name;
constexpr auto operator=(const auto&) {
return name;
}
};
constexpr std::string_view operator""_fmt(const char* str, std::size_t len) {
return {str, len};
}
constexpr udl_arg operator""_arg(const char* str, std::size_t len) {
const std::string_view sv{str, len};
return {sv};
}
constexpr auto has_valid_args(const auto& fmt, const auto&&... args) {
const std::array<std::string_view, sizeof...(args)> a{args...};
std::array<bool, sizeof...(args)> b{};
for (const auto& [m, name] : ctre::range<"\\{([A-Z_a-z][A-Z_a-z0-9]*)\\}">(fmt)) {
const auto it = std::find(std::cbegin(a), std::cend(a), name);
if (it == std::cend(a)) return false;
b[std::distance(std::cbegin(a), it)] = true;
}
return std::reduce(std::cbegin(b), std::cend(b), true, std::logical_and<>());
}
constexpr bool contains(auto fmt, auto arg) {
for(int i=0; i<fmt.arr.size(); i++) {
if( fmt.arr[i] == arg.first.arr[0] ) {
int j = 0;
for(; j<arg.first.arr.size(); j++)
if( fmt.arr[i+j] != arg.first.arr[j])
break;
if(j == arg.first.arr.size())
return true;
}
}
return false;
}
constexpr auto has_valid_args(auto fmt, auto... args) {
return (contains(fmt, args) & ... & true);
}
template<char...s> struct arg {
template<typename T>
std::pair<arg<s...>, T> constexpr operator=(T value) {
return {{}, value};
}
std::array<char, sizeof...(s)> arr{s...};
};
template<typename charT, charT...s>
arg<s...> constexpr operator"" _arg() { return {}; }
template<char...s> struct fmt {
std::array<char, sizeof...(s)> arr{s...};
};
template<typename charT, charT...s>
fmt<s...> constexpr operator"" _fmt() { return{}; }
constexpr auto operator""_fmt(const char* first_char, const std::size_t length) {
return std::string_view{first_char, first_char + length};
}
struct arg : std::string_view {
constexpr auto operator=(const auto&) { return *this; }
};
constexpr auto operator""_arg (const char* first_char, const std::size_t length) {
return arg(std::string_view{first_char, first_char + length});
}
consteval auto all_args_in_string(const auto &fmt) {
return true;
}
consteval auto all_args_in_string(const auto &fmt, const auto arg, const auto... args) {
const bool arg_in_string = fmt.find(arg) != std::string_view::npos;
return arg_in_string and all_args_in_string(fmt, args...);
}
consteval auto has_valid_args(const auto fmt, const auto... args) {
return all_args_in_string(fmt, args...);
struct arg {
constexpr auto& operator=(const auto&) { return *this; }
constexpr operator auto() const { return name; }
std::string_view name;
};
constexpr auto operator""_arg(const char* name, std::size_t size) {
return arg{.name = {name, size}};
}
constexpr auto operator""_fmt(const char* name, std::size_t size) {
return std::string_view{name, size};
}
constexpr auto has_valid_args(auto fmt, auto... args) {
return ((fmt.find(args) != std::string_view::npos) and ...);
}