如何在C++编译时检测const char*格式错误?

How to detect const char* format errors at C++ compile time?

提问人:qingl 提问时间:9/16/2023 最后编辑:qingl 更新时间:9/20/2023 访问量:102

问:

我正在尝试编写一个函数“my_func()”,该函数在编译时计算字符串中的字符数“a”,这使得当“a”的计数错误时代码无法编译。

我受到 C++ 标准库中函数的启发,该函数检查格式字符串中的数字。std::format(){}

我使用的编译器是msvc,C++20。

我下面的代码无法编译,因为我不知道如何实现这样的功能。那么我该如何修复功能呢?my_func()

template <size_t Size>
auto my_func(const char(&str)[Size]) -> void {
    // dosomething...

    constexpr size_t count = 0;
    const char* c = str;
    for (size_t i = 0; i < Size; ++i) {
        if (*c == 'a') {
            count++; // const values cannot be modified
        }
        c++;
    }

    // If the variable `count ` is not set to constexpr, an error will be reported here.
    static_assert(count == 2);

    // dosomething...
}

auto main() -> int {
    my_func("abc abc"); // is error
}

下一个

感谢@ecatmur的回答,但是当 str 转换为 .counting_string

我尝试将构造函数的模板参数传递给类,但这阻止了我在调用函数时找到匹配的重载函数。prepare

template <char Char, size_t Count, size_t Size>
struct counting_string {
    std::array<char, Size> m_chars;

    // NOLINTNEXTLINE(google-explicit-constructor)
    consteval explicit(false) counting_string(char const (&str)[Size]) {
        size_t count = 0;
        const char* c = str;
        for (size_t i = 0; i < Size; ++i) {
            if (*c == Char) {
                count++;
            }
            c++;
        }
        if (count != Count) {
            throw "invalid str";
        }
    }
};

template <size_t Size, class... Args>
auto prepare(const counting_string<'?', sizeof...(Args), Size> sql, Args... args) -> void {
// use sql as: sql.m_chars.data();
}

auto main() -> int {
    // call function error
    // prepare("insert into test (name, age) values (?, ?)", 1, 2);
}

下面的代码是我希望实现的最终结果。 在没有额外开销(编译时检查)的情况下,检查 SQL 语句中的占位符数量以避免错误。

template <char Char, size_t Count>
struct constexpr_counting_string {
    template <size_t Size>
    // NOLINTNEXTLINE(google-explicit-constructor)
    consteval explicit(false) constexpr_counting_string(char const (&str)[Size]) {
        size_t count = 0;
        const char* c = str;
        for (size_t i = 0; i < Size; ++i) {
            if (*c == Char) {
                count++;
            }
            c++;
        }
        if (count != Count) {
            throw "invalid str";
        }
    }
};

// prepare and bind
template <typename... Args>
auto prepare(statement_t& stmt, constexpr_counting_string<'?', sizeof...(Args)> sql, Args... args) noexcept -> code_t {
    // prepare
    code_t error = helper_prepare(stmt, sql);

    // bind
    auto do_bind = [](statement_t& stmt, size_t n, auto arg, code_t& error) noexcept {
        if (error != code_t::ok) {
            return;
        }

        using arg_type = decltype(arg);

        if constexpr (std::is_same_v<arg_type, uint64_t>) {
            error = stmt.bind(n, arg);
        }
        else if constexpr (std::is_same_v<arg_type, int64_t>) {
            error = stmt.bind(n, arg);
        }
        else if constexpr (std::is_same_v<arg_type, int>) {
            error = stmt.bind(n, arg);
        }
        else if constexpr (std::is_same_v<arg_type, const char*>) {
            error = stmt.bind(n, arg);
        }
        else {
            static_assert(std::is_same_v<arg_type, uint64_t>, "args error");
        }
    };

    size_t index = 0;
    (do_bind(stmt, index++, args, error), ...);
    return error;
}

auto main() -> int {
    prepare("insert into test (name, age) values (?, ?)", 1, 2);
}
C++ 模板 constexpr 编译时

评论

0赞 Remy Lebeau 9/16/2023
只是好奇,为什么在编译时使用指针进行迭代,而不仅仅是使用?const char*strstr[i]
0赞 qingl 9/16/2023
@Remy 勒博:这没有特别的原因。我认为它们是完全等价的。

答:

1赞 ecatmur 9/16/2023 #1

关键是检查 format-string 参数的构造函数格式是否正确,并且适合后面的参数。std::formatconstevalstd::format_stringstd::format

这意味着你需要颠倒你的逻辑;最简单的方法是编写一个类型,其构造函数仅接受包含 2 个 S 的字符串文本:counting_stringconsteval'a'

struct counting_string {
    template<unsigned Size>
    consteval explicit(false) counting_string(char const (&str)[Size]) {
        unsigned count = 0;
        const char* c = str;
        for (unsigned i = 0; i < Size; ++i) {
            if (*c == 'a') {
                count++; // const values cannot be modified
            }
            c++;
        }
        if (count != 2) throw "invalid str";
    }
};
auto my_func(counting_string str) -> void {
    ; // if we get here we know `str` contains 2 'a's
}

示例

对于样式点,您可能希望counting_string创建类模板

对于后续问题,问题是我们不能在构造函数中使用,因为它需要分配内存,并且(目前)内存分配不能从编译时传递到运行时。此外,我们不能使用静态字符串(或者,例如,),因为类模板参数推导 (CTAD) 不够智能,无法从函数模板函数参数推断类模板参数。(令人讨厌的是,CTAD 足够聪明,可以从非类型函数模板模板参数中做到这一点 - 但这在这里无济于事。std::stringconstevalstd::array<char, Size>Size

因此,唯一要做的就是将指向字符串文本的指针(或例如,a )存储在 中,并希望用户传入的参数具有足够长的生存期(如果它确实是字符串文本,那就没问题了 - 唯一的问题是它是否是静态字符串的数组数据成员或。事实上,这就是 std::format_string 的工作方式,所以它有同样的潜在问题。示例std::string_viewcounting_stringcharstd::array<char>

#include <string_view>
struct counting_string {
    template<unsigned Size>
    consteval explicit(false) counting_string(char const (&str)[Size]) : sv(str, Size) {
        unsigned count = 0;
        for (char const c : sv)
            if (c == 'a')
                count++; // const values cannot be modified
        if (count != 2) throw "invalid str";
    }
    std::string_view sv;
};

评论

0赞 qingl 9/17/2023
参数类型转换为后如何获取字符串的数据?counting_string
1赞 ecatmur 9/18/2023
@qingl好问题。我们不能使用 std::string,因为它分配的内存无法从编译时传播到运行时,因此我们需要使用 std::string_view(并希望调用者提供具有正确生存期的实际字符串文字): godbolt.org/z/8qTfjaYbY
1赞 Ted Lyngmo 9/16/2023 #2

您可以添加一个检查器:constexpr

#include <cstddef> // size_t
#include <utility> // index_sequence's

template <std::size_t Size>
constexpr std::size_t number_of_as(const char (&str)[Size]) { 
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
        return (... + (str[Is] == 'a')); // fold expression 
    }(std::make_index_sequence<Size>());
}
int main() {
    static_assert(number_of_as("abc abc") == 2); // pass
}