【问题标题】:How to pass a const char[] array as constexpr to a template?如何将 const char[] 数组作为 constexpr 传递给模板?
【发布时间】:2020-04-15 21:08:54
【问题描述】:

为了探索 C++ 抽象能力,我在另一个问题 How to stop template recursion while using parameter deduction? 上从 Jarod42 answer 中获取了这个示例:

#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 来改进它。我想删除 I constexpr 参数,因为它使递归总是从 const char[] 字符串大小减小到 0 :(用 @ 编译它987654336@ 并使用特殊标志向我们展示了模板扩展)

clang++ -Xclang -ast-print -fsyntax-only --std=c++17 test_debugger.cpp &gt; 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。

像Template Non-Type argument, C++11, restriction for string literals 这样的问题只是另一个问题,说我做不到,并建议另一个黑客。当 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[] 数组作为 constexpr 传递给 findlastslash()?


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

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

导致编译器抛出错误:

  1. error: ‘const char (&amp; path)[7]’ is not a valid template argument for type ‘const char (&amp;)[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&lt; int PathIndex, int PathLength, const char (&amp;path)[PathLength] &gt;

  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 &gt; 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

【问题讨论】:

  • 您不能永远将函数参数用作常量表达式。为什么这里会出人意料?
  • @DavisHerring,令人惊讶的是我无法将 const char[] 数组作为模板参数(不是函数参数)传递。在我链接的问题之后,我们看到 C++ 可能仅在 c++20 发布时才允许。
  • 这是否回答了您的(新)问题? Template Non-Type argument, C++11, restriction for string literals
  • @user:一旦你弄清楚你要问的是什么时候应该缩短它。您当然可以拥有该参数;您只是不能将字符串文字(或局部变量)作为参数传递。 (请注意,const char[n] 作为参数、模板或其他形式,仅代表一个指针,它可能有用也可能没用。)

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


【解决方案1】:

雅虎。我设法用 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 &gt; 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!");
}

虽然这不是理想的解决方案,但它可以与今天可用的 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 &gt; 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:
    

来自How to deduce the size of a compile time a const char string with C++ templates?的另一个例子

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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-15
    • 2018-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-14
    • 1970-01-01
    • 2019-09-24
    相关资源
    最近更新 更多