【问题标题】:How to pass const char[] arrays as constexpr template parameters using C++ STL libraries?如何使用 C++ STL 库将 const char[] 数组作为 constexpr 模板参数传递?
【发布时间】:2019-12-26 19:08:06
【问题描述】:

我有这个 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!" );
}

我想停止写/硬编码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!" );
}

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

你会如何解决这个问题?您会将 constexpr const char path[7] = "c/test"; 替换为 std::array 或 std::string_view 吗?

我尝试使用 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 &gt; 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? 上,我发现这个示例代码使用 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 类吗? C++ STL(标准模板库)没有任何东西可以用来代替这个常见/简单的任务?

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

  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.

【问题讨论】:

  • 请不要再说“constexpr 模板参数”——没有其他类型的。
  • @DavisHerring,这是一个 pleonasm 来表示同情并使其非常明显。如果我只是说 template parameters,那么有人可以考虑一下:template&lt;int Size&gt; function(const char (&amp;name)[Size]) 而不是 template&lt;const char (name)[Size]&gt; function() 之类的东西。但正如我添加的,它必须是 constexpr,不可能是前者,因为函数参数不能是 constexpr。
  • 对于给定的示例,template&lt; std::sise_t N&gt; constexpr int find_last_slash(const char (&amp;s)[N]) 可以完成这项工作。 (以及 C++20 中的 consteval 以避免可能的运行时调用)。
  • @user: "如果我只是说模板参数,有人可能会想到:template function(const char (&name)[Size]) 而不是 template function()." 他们认为这是错误的,因为name 不是模板参数。这就是为什么“模板参数”和“函数参数”有区别的原因。
  • @Jarod42,如果我理解正确,您在最初编写该示例时已经这样做了,但是无法使用我添加的修改,即使用 const char(&amp;s)[N] 直接在 constexpr 中。

标签: c++ arrays templates c++17 c++20


【解决方案1】:

我想停止写/硬编码 constexpr 数组大小为 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

您可以使用 SFINAE 或概念 (C++20)“保护”auto:

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

我真的需要定义自己的FixedString 类吗? C++ STL(标准模板库)没有任何东西可以用来代替这个常见/简单的任务?

目前似乎没有等同于FixedString。

【讨论】:

  • 公平的答案!但是我忘了告诉我我不喜欢使用 auto 因为使代码不太清晰。这就像阅读通常没有类型的 Python 代码(除非您使用允许添加类型标签的最新版本)。如果它有任何帮助,我宁愿将自己定义为我在问题中提出的示例 FixedString,而不是使用 auto。
  • C++20 允许使用概念,因此您可能使用CharArray 而不是auto,但FixedString 似乎更胜一筹。
  • 在 C++20 之前,正如您在另一个问题中已经提到的,您可以创建 char sequence type。
  • 如果我理解正确,那只是clang/gcc的扩展。我宁愿使用可以与标准的任何编译器投诉一起使用的东西。
  • 你能举个例子来说明它是如何使用概念的吗?
【解决方案2】:

对于它的价值,这是一个使用问题中提出的FixedString 类的示例。遗憾的是,这只适用于 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!" );
}

【讨论】:

    猜你喜欢
    • 2020-04-15
    • 2011-04-15
    • 2018-08-07
    • 1970-01-01
    • 1970-01-01
    • 2021-06-30
    • 1970-01-01
    • 1970-01-01
    • 2022-01-16
    相关资源
    最近更新 更多