【发布时间】: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 > 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 与以下任一提议一起发布时,可能会编写此问题的答案:
通常会找到类似下面这样的东西,它工作正常:(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()
{
导致编译器抛出错误:
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 addressnote: candidate template ignored: invalid explicitly-specified argument for template parameter 'path'
这些是第一个尝试使用 const char[] 数组作为模板参数引用的示例的编译器错误:template< int PathIndex, int PathLength, const char (&path)[PathLength] >
-
g++ -o main.exe --std=c++17 test_debugger.cpptest_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__) ^~~~~~~~~~~ -
clang++ -Xclang -ast-print -fsyntax-only --std=c++17 test_debugger.cpp > main.exetest_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 参数来停止递归。
一些相关问题:
【问题讨论】:
-
您不能永远将函数参数用作常量表达式。为什么这里会出人意料?
-
@DavisHerring,令人惊讶的是我无法将
const char[]数组作为模板参数(不是函数参数)传递。在我链接的问题之后,我们看到 C++ 可能仅在c++20发布时才允许。 -
@user:一旦你弄清楚你要问的是什么时候应该缩短它。您当然可以拥有该参数;您只是不能将字符串文字(或局部变量)作为参数传递。 (请注意,
const char[n]作为参数、模板或其他形式,仅代表一个指针,它可能有用也可能没用。)
标签: c++ arrays templates c++20