如何使用 C++ STL 库将 const char[] 数组作为 constexpr 模板参数传递?

How to pass const char[] arrays as constexpr template parameters using C++ STL libraries?

我有 C++ 17 标准的工作代码:

template< int PathIndex, int PathLength, const char (path)[PathLength] >
constexpr const int findlastslash()
{
    if constexpr( PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\' ) {
        return PathIndex;
    }
    else {
        return findlastslash<PathIndex - 1, PathLength, path>();
    }
}

template< int PathLength, const char (path)[PathLength] >
constexpr const int startfindlastslash()
{
    return findlastslash< PathLength - 1, PathLength, path >();
}

int main(int argc, char const *argv[]) {
    static constexpr const char path[7] = "c/test";
    static_assert( startfindlastslash< 7, path >() == 1, "Fail!" );
}

我想停止 writing/hard 编码 constexpr 数组大小 7,即,让模板元编程自行推断 constexpr const char[] 数组大小,而不必在任何地方都写出大小。例如,给定如下所示的内容:

template< int PathIndex, int PathLength, const char (path)[PathLength] >
constexpr const int findlastslash()
{
    if constexpr( PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\' ) {
        return PathIndex;
    }
    else {
        return findlastslash<PathIndex - 1, PathLength, path>();
    }
}

template< const char (path)[PathLength] >
constexpr const int startfindlastslash()
{
    return findlastslash< PathLength - 1, PathLength, path >();
}

template<int PathLength>
constexpr const char path[PathLength] = "c/test";

int main(int argc, char const *argv[]) {
    static_assert( startfindlastslash< path >() == 1, "Fail!" );
}

当然,上面的代码是完全无效的。但是,它是描述事物的简单方法的一个很好的近似值。

你会如何解决这个问题?你会用 std::arraystd::string_view[=79= 替换 constexpr const char path[7] = "c/test"; 吗]?

我尝试使用 std::string_view:

构建此代码
#include <string_view>

template< int PathIndex, std::string_view path >
constexpr const int findlastslash()
{
    if constexpr( PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\' ) {
        return PathIndex;
    }
    else {
        return findlastslash<PathIndex - 1, path>();
    }
}

template< std::string_view path >
constexpr const int startfindlastslash()
{
    return findlastslash< path.length() - 1, path >();
}

int main(int argc, char const *argv[]) {
    static constexpr std::string_view path{"c/test"};
    static_assert( startfindlastslash< path >() == 1, "Fail!" );
}

但是编译不通过:

  1. g++ -o main.exe --std=c++17 test_debugger.cpp

    test_debugger.cpp:3:43: error: ‘class std::basic_string_view<char>’ is not a valid type for a template non-type parameter
     template< int PathIndex, std::string_view path >
                                               ^~~~
    test_debugger.cpp:14:28: error: ‘class std::basic_string_view<char>’ is not a valid type for a template non-type parameter
     template< std::string_view path >
                                ^~~~
    test_debugger.cpp: In function ‘int main(int, const char**)’:
    test_debugger.cpp:22:47: error: no matching function for call to ‘startfindlastslash<path>()’
         static_assert( startfindlastslash< path >() == 1, "Fail!" );
                                                   ^
    test_debugger.cpp:15:21: note: candidate: template<<typeprefixerror>path> constexpr const int startfindlastslash()
     constexpr const int startfindlastslash()
                         ^~~~~~~~~~~~~~~~~~
    test_debugger.cpp:15:21: note:   template argument deduction/substitution failed:
    test_debugger.cpp:22:47: note: invalid template non-type parameter
         static_assert( startfindlastslash< path >() == 1, "Fail!" );
                                                   ^
    
  2. clang++ -Xclang -ast-print -fsyntax-only --std=c++17 test_debugger.cpp > main.exe

    test_debugger.cpp:3:43: error: a non-type template parameter cannot have type 'std::string_view' (aka 'basic_string_view<char>')
    template< int PathIndex, std::string_view path >
                                              ^
    test_debugger.cpp:14:28: error: a non-type template parameter cannot have type 'std::string_view' (aka 'basic_string_view<char>')
    template< std::string_view path >
                               ^
    test_debugger.cpp:15:21: error: no return statement in constexpr function
    constexpr const int startfindlastslash()
                        ^
    test_debugger.cpp:22:20: error: no matching function for call to 'startfindlastslash'
        static_assert( startfindlastslash< path >() == 1, "Fail!" );
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~
    test_debugger.cpp:15:21: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'path'
    constexpr const int startfindlastslash()
                        ^
    4 errors generated.
    

注意:我对查找字符串中的最后一个斜杠不感兴趣,因为算法正在执行此操作。我只是以这个愚蠢的例子为借口,以更好地了解使用 constexpr 模板参数可以做什么和不能做什么。

参考 (C++20) String literals as non-type template parameters example? I found this example code doing something cool with C++ 20: (https://godbolt.org/z/L0J2K2)

template<unsigned N>
struct FixedString {
    char buf[N + 1]{};
    constexpr FixedString(char const* s) {
        for (unsigned i = 0; i != N; ++i) buf[i] = s[i];
    }
    constexpr operator char const*() const { return buf; }
};
template<unsigned N> FixedString(char const (&)[N]) -> FixedString<N - 1>;

template<FixedString T>
class Foo {
    static constexpr char const* Name = T;
public:
    void hello() const;
};

int main() {
    Foo<"Hello!"> foo;
    foo.hello();
}

我真的需要定义自己的 FixedString class 吗? C++ STL(标准模板库)没有任何可以替代此 common/simple 任务的东西?

为了参考,我找到了这个不错的相关第三方库:

  1. https://github.com/irrequietus/typestring 对于 C++11/14 strings for direct use in template parameter lists, template metaprogramming.
  2. https://github.com/hanickadot/compile-time-regular-expressions A Compile time PCRE (almost) compatible regular expression matcher.

I would like to stop writing/hard coding the constexpr array size 7

在 C++17 中,您可以使用 auto 作为非模板参数并摆脱硬编码 7:

template <std::size_t PathIndex, const auto& path>
constexpr std::size_t findlastslash()
{
    if constexpr (PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\') {
        return PathIndex;
    }
    else {
        return findlastslash<PathIndex - 1, path>();
    }
}

template <const auto& path>
constexpr std::size_t startfindlastslash()
{
    return findlastslash<std::extent_v<std::remove_reference_t<decltype(path)>> - 1, path >();
}

int main() {
    static constexpr const char path[] = "c/test";
    static_assert(startfindlastslash<path>() == 1, "Fail!" );
}

Demo

您可能 "protect" auto SFINAE 或概念 (C++20):

template <typename T, std::size_t N>
constexpr std::true_type is_c_array_impl(const T(&)[N]) { return {}; }

template <typename T>
constexpr std::false_type is_c_array_impl(const T&) { return {}; }

template <typename T>
constexpr auto is_c_array() -> decltype(is_c_array_impl(std::declval<const T&>())) {return {};}

template <typename T>
concept CArrayRef = (bool) is_c_array<const T&>() && std::is_reference_v<T>;

template <std::size_t PathIndex, const auto& path>
constexpr std::size_t findlastslash() requires (CArrayRef<decltype(path)>)
{
    if constexpr (PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\') {
        return PathIndex;
    }
    else {
        return findlastslash<PathIndex - 1, path>();
    }
}

template <const auto& path>
constexpr std::size_t startfindlastslash() requires (CArrayRef<decltype(path)>)
{
    return findlastslash<std::extent_v<std::remove_reference_t<decltype(path)>> - 1, path >();
}

Demo

Do I really need to define my own FixedString class? The C++ STL (Standard Template Library) does not have anything which can be used instead for this common/simple task?

目前似乎没有等同于FixedString

对于它的价值,这是一个使用问题中提出的 FixedString class 的示例。遗憾的是,这只能用 GCC 9.1 或更高版本编译。甚至现在的 Clang 9 或 Clang trunk 都无法构建它,因为它需要 C++20 特性。

https://godbolt.org/z/eZy2eY(对于 GCC 9.1)

template<unsigned N>
struct FixedString
{
    char buf[N + 1]{};
    int length = N;

    constexpr FixedString(char const* string)
    {
        for (unsigned index = 0; index < N; ++index) {
            buf[index] = string[index];
        }
    }
    constexpr operator char const*() const { return buf; }
};

template<unsigned N>
FixedString(char const (&)[N]) -> FixedString<N - 1>;
template< int PathIndex, FixedString path >
constexpr const int findlastslash()
{
    if constexpr( PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\' ) {
        return PathIndex;
    }
    else {
        return findlastslash<PathIndex - 1, path>();
    }
}

template< FixedString path >
constexpr const int startfindlastslash() 
{
    return findlastslash< path.length - 1, path >();
}

int main(int argc, char const *argv[]) {
    static_assert( startfindlastslash< "c/test" >() == 1, "Fail!" );
}