In order to explore C++ abstraction power, I took this example from Jarod42 answer on this other question How to stop template recursion while using parameter deduction?:
#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 );
}
This this example, I am trying to improve it by removing the constexpr parameter I
from the function findlastslash_impl
. I would like to remove the I
constexpr parameter because it makes the recursion always go from const char[]
string size down to 0: (compiling it with clang
and using special flags shows us the template expansions)
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");
}
Then, if convert the function parameter path
to a constexpr, I can remove the I
constexpr parameter and I am allowed to only expand the findlastslash_impl
function recursion exactly to the location of the last slash instead of always going from const char[]
string size down to 0.
Questions like Template Non-Type argument, C++11, restriction for string literals are just another question saying I cannot do it and suggests another hack. An answer to this question would may be written when the C++20 or C++23 is released with either one of these proposals:
It is common to find something like this bellow, which works fine: (Passing an array by reference to template function in c++)
template<class T>
T sum_array(T (&a)[10], int size)
{ ...
But I would like to do something like this bellow instead, to be able to use the array in a if constexpr
:
template<class T, T (&a)[10]>
T sum_array(int size)
{ ...
Here, I got an example I am trying to apply this concept:
#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 );
}
Compiling it with C++17
standard, I got the compiler saying: error: ‘"c/test"’ is not a valid template argument for type ‘const char*’ because string literals can never be used in this context
How can I pass an const char[]
array as a constexpr to findlastslash()
?
Initially I had tried passing the const char[]
array as a reference:
template< int PathIndex, int PathLength, const char (&path)[PathLength] >
constexpr const int findlastslash()
{
Leading the compiler to throw the errors:
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
note: candidate template ignored: invalid explicitly-specified argument for template parameter 'path'
These are the compiler errors for the first example trying to use the const char[]
array as a template parameter reference: template< int PathIndex, int PathLength, const char (&path)[PathLength] >
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__) ^~~~~~~~~~~
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.
Update
After I tried to couple with what C++ 17
could offer, this is my code:
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!" );
}
Which miserably fails with:
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.
Even using a if constexpr
, C++ cannot stop the recursion by itself checking the PathIndex
parameter.
Some related questions: