-1

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:

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

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:

  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'

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] >

  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.
    

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:

  1. C++11 Local static values not working as template arguments
  2. Template on address of variable with static storage
Evandro Coan
  • 8,560
  • 11
  • 83
  • 144
  • 2
    Related to: [Passing a string literal as a parameter to a C++ template class](https://stackoverflow.com/questions/2033110/passing-a-string-literal-as-a-parameter-to-a-c-template-class) – Evandro Coan Dec 25 '19 at 03:04
  • You can’t *ever* use a function parameter as a constant expression. Why is this a surprise here? – Davis Herring Dec 25 '19 at 07:12
  • @DavisHerring, The surprise is that I cannot pass the **`const char[]`** array as a template parameter (not function parameter). Following the my linked question, we see that C++ will maybe only allow it when the **`c++20`** is released. – Evandro Coan Dec 25 '19 at 13:09
  • Does this answer your (new) question? [Template Non-Type argument, C++11, restriction for string literals](https://stackoverflow.com/questions/28763443/template-non-type-argument-c11-restriction-for-string-literals) – Davis Herring Dec 25 '19 at 14:16
  • Your question doesn’t mention any of that or have the right version tags for it—it doesn’t even say “string literal” except in the error message, and there are other `const char[…]` objects for which the answer is different. Please edit again, **reducing** the question to *one* clear point. If you’re after an answer from C++23, you need to wait a couple of years before asking (but there is a non-trivial C++20 answer to *certain* variations on this theme). – Davis Herring Dec 25 '19 at 14:34
  • @user: Is this question about arrays in general, `const char[]` specifically, or string literals in particular? Because those are three different questions. – Nicol Bolas Dec 25 '19 at 15:03
  • @NicolBolas, it is about the c-style arrays **`const char[]`** as **`constexpr`** strings with **`function()`** – Evandro Coan Dec 25 '19 at 15:06
  • @user: That still kind of misses the point. Is it *especially* important that the type be `const char[]`? Is it important that this `function` takes *only* a string literal, rather than any `constexpr` string which might be generated by any `constexpr` process? – Nicol Bolas Dec 25 '19 at 15:16
  • @NicolBolas, I edited the question explaining the reasons. – Evandro Coan Dec 25 '19 at 15:56
  • @user: "*I would like to remove the `I` constexpr parameter*" So... why are you still trying to use recursion? To remove the `I` template parameter means you want to turn the recursive implementation into iteration. Recursion *does not work* if you remove the argument that defines the termination of the recursive algorithm. – Nicol Bolas Dec 25 '19 at 16:19
  • @user: Also, this explanation only raises more questions. How does your question about passing a string literal/`const char[]`/array as a template parameter relate to some function that takes a string as a regular parameter? I fail to see the connection here. In particular: "*it makes the recursion always go from const char[] string size down to 0*." Why exactly does that *matter*? – Nicol Bolas Dec 25 '19 at 16:22
  • @NicolBolas, see the code example after the text **`Here, I got an example I am trying to apply this concept`**. There I had removed the **`I`** recursive parameter and had added **`const char (path)[PathLength]`** in the place of it. Now look at the body of the recursive function and compare it with the original one at the beginning of the question. – Evandro Coan Dec 25 '19 at 16:25
  • @user: You're still missing the question. Why are you trying to implement it that way, rather than the obvious, non-recursive, non-string-template-parameter way that definitely works? Why do you want to turn a function that takes a string by function parameter into a function that takes a string by template parameter? That's the part that doesn't make sense. – Nicol Bolas Dec 25 '19 at 16:26
  • @NicolBolas, to explore how much I can go deep into C++ inner workings without breaking into some limitation. Sadly we just see we cannot pass **`const char[]`** as template constexpr parameters. On the proposal paper ["String literals as non-type template parameters"](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0424r2.pdf) you will see more real and concrete examples of a real world usage for things like **`function<"myconstchar">()`**. My initial example here is just a silly experiment to see how much deep the rabbit hole goes. Sadly it did not go much deep. – Evandro Coan Dec 25 '19 at 16:33
  • @user: You certainly **can** have `const char (&)[]` template parameters, with or without a size. Your terminology is very confusing: there is no template parameter that isn’t “constexpr”. Are you just trying to ask what *C++20* does to “allow” string literals as template arguments? If so, please **say** so (in fewer than 300 words)! – Davis Herring Dec 25 '19 at 17:44
  • @DavisHerring, My question was bellow 300 words when I fist posted, but comments asking more questions kept coming. I cannot have **`const char (&)[]`** as template parameter (constexpr). There are two examples on my question where I tried to do so and the compiler throw right in my face: 1. **`error: ‘"c/test"’ is not a valid template argument for type ‘const char*’ because string literals can never be used in this context`**; 2 **`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`**. – Evandro Coan Dec 25 '19 at 17:54
  • 1
    @user: Once you figure out what you’re asking about is when you should shorten it. You certainly can have that parameter; you just can’t pass a string literal (or a local variable) as an argument for it. (Note that `const char[n]` as a parameter, template or otherwise, represents only a pointer, which might or might not be useful.) – Davis Herring Dec 25 '19 at 18:02
  • I wrote an answer which shows how it could be done with the C++ standard as of **`C++ 17`** and how I would expect to be the best way to handle/write this code with an fictitious **`C++`** standard which would allow to directly use a template constexpr as **`function<"string">()`** – Evandro Coan Dec 26 '19 at 01:23

1 Answers1

2

Yahoo. I managed to make something to work with what the C++ 17 standard allow us:

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!" );
}

Which expands to this:

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!");
}

While this is not the ideal solution, this is something which "works" with the C++ available today.

The best answer would be the C++ standard allowing us to write a code which can be easily maintainable like this:

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

However, such nice code miserably fails with:

  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:
    

Another example from 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;
}
Evandro Coan
  • 8,560
  • 11
  • 83
  • 144