2

I have this working code for C++ 17 standard:

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

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

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

I would like to stop writing/hard coding the constexpr array size 7, i.e., make the template meta-programming deduce by itself the constexpr const char[] array size instead of having to write the size everywhere. For example, given something looking like the following:

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

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

template<int PathLength>
constexpr const char path[PathLength] = "c/test";

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

Of course, the code just above is utterly invalid. However, it is a good approximation of a easy way to describe things.

How would you solve this problem? Would you replace constexpr const char path[7] = "c/test"; by a std::array or std::string_view?

I tried building this code using std::string_view:

#include <string_view>

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

template< std::string_view path >
constexpr const int startfindlastslash()
{
    return findlastslash< path.length() - 1, path >();
}

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

But it does not compile:

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

    test_debugger.cpp:3:43: error: ‘class std::basic_string_view<char>’ is not a valid type for a template non-type parameter
     template< int PathIndex, std::string_view path >
                                               ^~~~
    test_debugger.cpp:14:28: error: ‘class std::basic_string_view<char>’ is not a valid type for a template non-type parameter
     template< std::string_view path >
                                ^~~~
    test_debugger.cpp: In function ‘int main(int, const char**)’:
    test_debugger.cpp:22:47: error: no matching function for call to ‘startfindlastslash<path>()’
         static_assert( startfindlastslash< path >() == 1, "Fail!" );
                                                   ^
    test_debugger.cpp:15:21: note: candidate: template<<typeprefixerror>path> constexpr const int startfindlastslash()
     constexpr const int startfindlastslash()
                         ^~~~~~~~~~~~~~~~~~
    test_debugger.cpp:15:21: note:   template argument deduction/substitution failed:
    test_debugger.cpp:22:47: note: invalid template non-type parameter
         static_assert( startfindlastslash< path >() == 1, "Fail!" );
                                                   ^
    
  2. clang++ -Xclang -ast-print -fsyntax-only --std=c++17 test_debugger.cpp > main.exe

    test_debugger.cpp:3:43: error: a non-type template parameter cannot have type 'std::string_view' (aka 'basic_string_view<char>')
    template< int PathIndex, std::string_view path >
                                              ^
    test_debugger.cpp:14:28: error: a non-type template parameter cannot have type 'std::string_view' (aka 'basic_string_view<char>')
    template< std::string_view path >
                               ^
    test_debugger.cpp:15:21: error: no return statement in constexpr function
    constexpr const int startfindlastslash()
                        ^
    test_debugger.cpp:22:20: error: no matching function for call to 'startfindlastslash'
        static_assert( startfindlastslash< path >() == 1, "Fail!" );
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~
    test_debugger.cpp:15:21: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'path'
    constexpr const int startfindlastslash()
                        ^
    4 errors generated.
    

Note: I am not interested in finding the last slash on the string as the algorithm is doing. I just took this silly example as an excuse to learn better what can and cannot be done with constexpr template parameters.

For reference, on (C++20) String literals as non-type template parameters example? I found this example code doing something cool with C++ 20: (https://godbolt.org/z/L0J2K2)

template<unsigned N>
struct FixedString {
    char buf[N + 1]{};
    constexpr FixedString(char const* s) {
        for (unsigned i = 0; i != N; ++i) buf[i] = s[i];
    }
    constexpr operator char const*() const { return buf; }
};
template<unsigned N> FixedString(char const (&)[N]) -> FixedString<N - 1>;

template<FixedString T>
class Foo {
    static constexpr char const* Name = T;
public:
    void hello() const;
};

int main() {
    Foo<"Hello!"> foo;
    foo.hello();
}

Do I really need to define my own FixedString class? The C++ STL (Standard Template Library) does not have anything which can be used instead for this common/simple task?

For reference, I found this nice related third part libraries:

  1. https://github.com/irrequietus/typestring for C++11/14 strings for direct use in template parameter lists, template metaprogramming.
  2. https://github.com/hanickadot/compile-time-regular-expressions A Compile time PCRE (almost) compatible regular expression matcher.
Evandro Coan
  • 8,560
  • 11
  • 83
  • 144
  • 4
    Please stop saying “constexpr template parameters”—there isn’t any other kind. – Davis Herring Dec 26 '19 at 19:27
  • @DavisHerring, It is a **`pleonasm`** to empathize and make it pretty obvious. If I just said **`template parameters`** someone could think about this: **`template function(const char (&name)[Size])`** instead of something like **`template function()`**. But as I added it has to be **`constexpr`**, there is no way it could be the former because function parameters cannot be **`constexpr`**. – Evandro Coan Dec 26 '19 at 19:55
  • 2
    For the given example, `template< std::sise_t N> constexpr int find_last_slash(const char (&s)[N])` would do the job. (and [`consteval`](https://en.cppreference.com/w/cpp/language/consteval) in C++20 to avoid possible runtime calls). – Jarod42 Dec 26 '19 at 20:10
  • 1
    @user: "*If I just said template parameters someone could think about this: template function(const char (&name)[Size]) instead of something like template function().*" And they would be wrong to think that because `name` would not be a template parameter. This is why there is a distinction between "template parameter" and "function parameter". – Nicol Bolas Dec 26 '19 at 21:11
  • @Jarod42, If I understand correctly, you already did it when you wrote that example originally, but it would not be possible to use the modification I added, i.e., using the **`const char(&s)[N]`** directly in a constexpr. – Evandro Coan Dec 26 '19 at 22:59
  • there's no constexpr const ... , use either one –  Dec 27 '19 at 00:01
  • @nonock, For reference, **`constexpr`** is an abbreviation to **`constant expression`**. We could call **`constexpr const`** a compiler pleonasm. The compiler did not get in my way making my life miserable when I used it for the first time, then I just kept going with it «~‿~» – Evandro Coan Dec 27 '19 at 00:07
  • @user: By definition, a "pleonasm" represents a thing that is it is unnecessarily verbose and thus serves no actual purpose, so you should stop doing it because it merely takes up space without communicating useful information. – Nicol Bolas Dec 27 '19 at 04:11
  • @NicolBolas, You are right. I stopped adding it. At the beginning it was fun, but after a while it get boring. – Evandro Coan Dec 27 '19 at 04:20
  • @NicolBolas, After I removed the `const` from my `constexpr const char*`, the compiler started complaining: **`warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]`**, so I am adding my **`constexpr const`** back. – Evandro Coan Dec 27 '19 at 17:34
  • @user: Don't forget how `const` works with pointers. `const char *` says that the `char` is `const`, not the pointer. `constexpr T*` says that the pointer is a constant expression. – Nicol Bolas Dec 27 '19 at 18:09

2 Answers2

4

I would like to stop writing/hard coding the constexpr array size 7

In C++17, you might use auto as non template parameter and get rid of hard-coded 7:

template <std::size_t PathIndex, const auto& path>
constexpr std::size_t findlastslash()
{
    if constexpr (PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\\') {
        return PathIndex;
    }
    else {
        return findlastslash<PathIndex - 1, path>();
    }
}

template <const auto& path>
constexpr std::size_t startfindlastslash()
{
    return findlastslash<std::extent_v<std::remove_reference_t<decltype(path)>> - 1, path >();
}

int main() {
    static constexpr const char path[] = "c/test";
    static_assert(startfindlastslash<path>() == 1, "Fail!" );
}

Demo

You might "protect" the auto with SFINAE or with concept (C++20):

template <typename T, std::size_t N>
constexpr std::true_type is_c_array_impl(const T(&)[N]) { return {}; }

template <typename T>
constexpr std::false_type is_c_array_impl(const T&) { return {}; }

template <typename T>
constexpr auto is_c_array() -> decltype(is_c_array_impl(std::declval<const T&>())) {return {};}

template <typename T>
concept CArrayRef = (bool) is_c_array<const T&>() && std::is_reference_v<T>;

template <std::size_t PathIndex, const auto& path>
constexpr std::size_t findlastslash() requires (CArrayRef<decltype(path)>)
{
    if constexpr (PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\\') {
        return PathIndex;
    }
    else {
        return findlastslash<PathIndex - 1, path>();
    }
}

template <const auto& path>
constexpr std::size_t startfindlastslash() requires (CArrayRef<decltype(path)>)
{
    return findlastslash<std::extent_v<std::remove_reference_t<decltype(path)>> - 1, path >();
}

Demo

Do I really need to define my own FixedString class? The C++ STL (Standard Template Library) does not have anything which can be used instead for this common/simple task?

It seems there is not currently equivalent to FixedString.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Fair answer! But I forgot to tell I do not like using auto because makes the code less clear. It is like reading Python code where usually there are no types (unless you use the latest version which allows you to add type tags). If it will help in any way, I would rather define myself some class as the example **`FixedString`** which I presented in the question, rather than use auto. – Evandro Coan Dec 26 '19 at 19:54
  • 1
    C++20 would allow concept, so you might have `CharArray` instead of `auto`, but `FixedString` seems superior. – Jarod42 Dec 26 '19 at 19:57
  • 1
    Prior to C++20, as already mentioned in another of your question, you might create [char sequence type](https://stackoverflow.com/a/58868005/2684539). – Jarod42 Dec 26 '19 at 20:02
  • If I understand correctly, that is only a extension for clang/gcc. I rather use something which can work with any compiler complaint with the standard. – Evandro Coan Dec 26 '19 at 22:49
  • Can you give an example about how it would be using concept? – Evandro Coan Dec 27 '19 at 00:27
  • gcc/clang extension, or MACRO usage (with hard coded length limit). – Jarod42 Dec 27 '19 at 08:16
  • Thanks! Can you add the codo into this answer or a new answer? It is just in case that web site go off-line/dead some day. In that case, the code would be lost. – Evandro Coan Dec 27 '19 at 13:30
  • This concept does not work in Visual Studio with C++20 enabled. It says `'startfindlastslash': the associated constraints are not satisfied` – Youda008 Apr 28 '22 at 08:51
  • @Youda008: extra parents fixes the issue [Demo](https://godbolt.org/z/EcrhbvY9v). – Jarod42 Apr 28 '22 at 09:07
  • I don't know why, but msvc is inconsistent on `decltype(path)` [Demo](https://godbolt.org/z/x17EKMdKx). – Jarod42 Apr 28 '22 at 09:37
0

For what it is worth, here it is an example using the FixedString class presented on the question. Sadly, this only compiles with GCC 9.1 or above. Not even Clang 9 or Clang trunk as of today can build this as it requires C++20 features.

https://godbolt.org/z/eZy2eY (for GCC 9.1)

template<unsigned N>
struct FixedString
{
    char buf[N + 1]{};
    int length = N;

    constexpr FixedString(char const* string)
    {
        for (unsigned index = 0; index < N; ++index) {
            buf[index] = string[index];
        }
    }
    constexpr operator char const*() const { return buf; }
};

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

template< FixedString path >
constexpr const int startfindlastslash() 
{
    return findlastslash< path.length - 1, path >();
}

int main(int argc, char const *argv[]) {
    static_assert( startfindlastslash< "c/test" >() == 1, "Fail!" );
}
Evandro Coan
  • 8,560
  • 11
  • 83
  • 144