如何将 const char[] 数组作为 constexpr 传递给模板?

How to pass a const char[] array as constexpr to a template?

为了探索 C++ 的抽象能力,我从 on this other question :

拿了这个例子
#define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__)

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

int main(int argc, char const *argv[]) {
    STATIC_ASSERT( findlastslash( "c/test" ) == 2 );
}

这个例子,我试图通过从函数 findlastslash_impl[= 中删除 constexpr 参数 I 来改进它134=]。我想删除 I constexpr 参数,因为它使递归总是从 const char[] 字符串大小下降到0:(用 clang 编译它并使用特殊标志向我们展示模板扩展)

clang++ -Xclang -ast-print -fsyntax-only --std=c++17 test_debugger.cpp > main.exe

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

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

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

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

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

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

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

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

template<> 
constexpr const int findlastslash<7, 0>(const char (&path)[7]) {
    if (0 == 0) {
        return 0;
    }
}
int main(int argc, const char *argv[]) {
    static_assert(findlastslash("c/test") == 2, 
            "findlastslash( \"c/test\" ) == 2");
}

然后,如果将函数参数 path 转换为 constexpr,我可以删除 I constexpr 参数,我只能将 findlastslash_impl 函数递归扩展到最后一个斜杠的位置,而不是总是从 const char[] 字符串大小减小到 0。

这样的问题只是另一个问题,说我做不到,并建议另一个 hack。当 C++20 或 C++23 与这些提案之一一起发布时,可能会写下这个问题的答案:

  1. "String literals as non-type template parameters"
  2. "Class Types in Non-Type Template Parameters"

通常会发现像下面这样的东西,效果很好:(Passing an array by reference to template function in c++)

template<class T>
T sum_array(T (&a)[10], int size)
{ ...

但我想做类似下面这样的事情,以便能够在 if constexpr:

中使用数组
template<class T, T (&a)[10]>
T sum_array(int size)
{ ...

在这里,我有一个例子,我正在尝试应用这个概念:

#define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__)

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

int main(int argc, char const *argv[])
{
    STATIC_ASSERT( findlastslash< 6, 6, "c/test" >() == 2 );
}

C++17标准编译它,我得到编译器说:error: ‘"c/test"’ is not a valid template argument for type ‘const char*’ because string literals can never be used in this context

如何将 const char[] 数组作为常量传递给 findlastslash()


最初我尝试传递 const char[] 数组作为参考:

template< int PathIndex, int PathLength, const char (&path)[PathLength] >
constexpr const int findlastslash()
{

导致编译器抛出错误:

  1. error: ‘const char (& path)[7]’ is not a valid template argument for type ‘const char (&)[7]’ because a reference variable does not have a constant address
  2. note: candidate template ignored: invalid explicitly-specified argument for template parameter 'path'

这些是第一个尝试使用 const char[] 数组作为模板参数引用的示例的编译器错误:template< int PathIndex, int PathLength, const char (&path)[PathLength] >

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

    test_debugger.cpp: In function ‘int main(int, const char**)’:
    test_debugger.cpp:14:52: error: no matching function for call to ‘findlastslash<6, 6, "c/test">()’
         STATIC_ASSERT( findlastslash< 6, 6, "c/test" >() == 2 );
                                                        ^
    test_debugger.cpp:1:42: note: in definition of macro ‘STATIC_ASSERT’
     #define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__)
                                              ^~~~~~~~~~~
    test_debugger.cpp:4:21: note: candidate: template<int PathIndex, int PathLength, const char* path> constexpr const int findlastslash()
     constexpr const int findlastslash()
                         ^~~~~~~~~~~~~
    test_debugger.cpp:4:21: note:   template argument deduction/substitution failed:
    test_debugger.cpp:14:52: error: ‘"c/test"’ is not a valid template argument for type ‘const char*’ because string literals can never be used in this context
         STATIC_ASSERT( findlastslash< 6, 6, "c/test" >() == 2 );
                                                        ^
    test_debugger.cpp:1:42: note: in definition of macro ‘STATIC_ASSERT’
     #define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__)
                                              ^~~~~~~~~~~
    
  2. clang++ -Xclang -ast-print -fsyntax-only --std=c++17 test_debugger.cpp > main.exe

    test_debugger.cpp:14:20: error: no matching function for call to 'findlastslash'
        STATIC_ASSERT( findlastslash< 6, 6, "c/test" >() == 2 );
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    test_debugger.cpp:1:42: note: expanded from macro 'STATIC_ASSERT'
    #define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__)
                                             ^~~~~~~~~~~
    test_debugger.cpp:4:21: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'path'
    constexpr const int findlastslash()
                        ^
    1 error generated.
    

更新

在我尝试结合 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;
    }

    return findlastslash<PathIndex - 1, PathLength, path>();
}

constexpr const char path[] = "c/test";

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

悲惨地失败了:

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

test_debugger.cpp: In function ‘int main(int, const char**)’:
test_debugger.cpp:15:5: error: static assertion failed: Fail!
     static_assert( findlastslash< 6, 6, path >() == 2, "Fail!" );
     ^~~~~~~~~~~~~
test_debugger.cpp: In instantiation of ‘constexpr const int findlastslash() [with int PathIndex = -898; int PathLength = 6; const char* path = (& path)]’:
test_debugger.cpp:8:58:   recursively required from ‘constexpr const int findlastslash() [with int PathIndex = 1; int PathLength = 6; const char* path = (& path)]’
test_debugger.cpp:8:58:   required from here
test_debugger.cpp:8:58: fatal error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum)
     return findlastslash<PathIndex - 1, PathLength, path>();
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
compilation terminated.

即使使用 if constexpr,C++ 也无法通过自身检查 PathIndex 参数来停止递归。

一些相关问题:

  1. C++11 Local static values not working as template arguments
  2. Template on address of variable with static storage

雅虎。我设法做了一些与 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>();
    }
}

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

扩展为:

clang++ -Xclang -ast-print -fsyntax-only --std=c++17 test_debugger.cpp > main.exe

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

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

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

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

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

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

template<> 
constexpr const int findlastslash<1, 7, &path>() 
{
    if (1 < 1 || path[1] == '/' || path[1] == '\') {
        return 1;
    }
}

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

虽然这不是理想的解决方案,但这是 "works" 可用的 C++

最好的答案是 C++ 标准,它允许我们编写易于维护的代码,如下所示:

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

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

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

然而,这么好的代码却惨遭失败:

  1. clang++ -Xclang -ast-print -fsyntax-only --std=c++17 test_debugger.cpp > main.exe

    test_debugger.cpp:1:29: error: use of undeclared identifier 'PathLength'
    template< const char (path)[PathLength], int PathIndex >
                                ^
    test_debugger.cpp:4:23: error: subscripted value is not an array, pointer, or vector
        if constexpr (path[PathIndex - 1] == '/' || path[PathIndex - 1] == '\') {
                      ~~~~^~~~~~~~~~~~~~
    test_debugger.cpp:4:53: error: subscripted value is not an array, pointer, or vector
        if constexpr (path[PathIndex - 1] == '/' || path[PathIndex - 1] == '\') {
                                                    ~~~~^~~~~~~~~~~~~~
    test_debugger.cpp:8:45: error: use of undeclared identifier 'PathLength'
            return findlastslash<PathIndex - 1, PathLength, path>();
                                                ^
    test_debugger.cpp:12:29: error: use of undeclared identifier 'PathLength'
    template< const char (path)[PathLength] >
                                ^
    test_debugger.cpp:15:33: error: use of undeclared identifier 'PathLength'
        return findlastslash< path, PathLength >();
                                    ^
    test_debugger.cpp:13:21: error: no return statement in constexpr function
    constexpr const int startfindlastslash()
                        ^
    test_debugger.cpp:19:20: error: no matching function for call to 'startfindlastslash'
        static_assert( startfindlastslash< "c/test" >() == 1, "Fail" );
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    test_debugger.cpp:13:21: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'path'
    constexpr const int startfindlastslash()
                        ^
    8 errors generated.
    
  2. g++ -o main.exe --std=c++17 test_debugger.cpp

    test_debugger.cpp:1:29: error: ‘PathLength’ was not declared in this scope
     template< const char (path)[PathLength], int PathIndex >
                                 ^~~~~~~~~~
    test_debugger.cpp: In function ‘constexpr const int findlastslash()’:
    test_debugger.cpp:4:19: error: ‘path’ was not declared in this scope
         if constexpr (path[PathIndex - 1] == '/' || path[PathIndex - 1] == '\') {
                       ^~~~
    test_debugger.cpp:8:45: error: ‘PathLength’ was not declared in this scope
             return findlastslash<PathIndex - 1, PathLength, path>();
                                                 ^~~~~~~~~~
    test_debugger.cpp:8:45: note: suggested alternative: ‘PathIndex’
             return findlastslash<PathIndex - 1, PathLength, path>();
                                                 ^~~~~~~~~~
                                                 PathIndex
    test_debugger.cpp: At global scope:
    test_debugger.cpp:12:29: error: ‘PathLength’ was not declared in this scope
     template< const char (path)[PathLength] >
                                 ^~~~~~~~~~
    test_debugger.cpp: In function ‘constexpr const int startfindlastslash()’:
    test_debugger.cpp:15:27: error: ‘path’ was not declared in this scope
         return findlastslash< path, PathLength >();
                               ^~~~
    test_debugger.cpp:15:33: error: ‘PathLength’ was not declared in this scope
         return findlastslash< path, PathLength >();
                                     ^~~~~~~~~~
    test_debugger.cpp: In function ‘int main(int, const char**)’:
    test_debugger.cpp:19:51: error: no matching function for call to ‘startfindlastslash<"c/test">()’
         static_assert( startfindlastslash< "c/test" >() == 1, "Fail" );
                                                       ^
    test_debugger.cpp:13:21: note: candidate: template<<declaration error> > constexpr const int startfindlastslash()
     constexpr const int startfindlastslash()
                         ^~~~~~~~~~~~~~~~~~
    test_debugger.cpp:13:21: note:   template argument deduction/substitution failed:
    

来自

的另一个示例
constexpr unsigned int requires_inRange(unsigned int i, unsigned int len) {
    return i >= len ? throw i : i;
}

class StrWrap
{
    unsigned size_;
    const char * begin_;

public:
    template< unsigned N >
    constexpr StrWrap( const char(&arr)[N] ) : begin_(arr), size_(N - 1) {
        static_assert( N >= 1, "not a string literal");
    }

    constexpr char operator[]( unsigned i ) {
        return requires_inRange(i, size_), begin_[i];
    }

    constexpr operator const char *() {
        return begin_;
    }

    constexpr unsigned size() {
        return size_;
    }
};

constexpr unsigned count( StrWrap str, char c, unsigned i = 0, unsigned ans = 0 )
{
    return i == str.size() ? ans :
               str[i] == c ? count(str, c, i + 1, ans + 1) :
                             count(str, c, i + 1, ans);
}

int main(int argc, char const *argv[])
{
    static_assert( count("dude", 'd' ) == 2, "d != 2" );
    return 0;
}