0

I have this code I took from __FILE__ macro manipulation handling at compile time:

#define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__)

constexpr const char * const strend(const char * const str) {
    return *str ? strend(str + 1) : str;
}

constexpr const char * const fromlastslash(const char * const start, const char * const end) {
    return (end >= start && *end != '/' && *end != '\\') ? fromlastslash(start, end - 1) : (end + 1);
}

constexpr const char * const pathlast(const char * const path) {
    return fromlastslash(path, strend(path));
}

int constexpr length(const char* str) {
    return *str ? 1 + length(str + 1) : 0;
}

int main(int argc, char const *argv[])
{
    constexpr const char* myExpression = pathlast( "cppdebugger/test_debugger.cpp" );
    STATIC_ASSERT( length(myExpression) == 17 );
}

Which works fine. But instead of using the constexpr length(const char* str) recursive function, I would like to use this other one (length( char const (&)[N] )), which deduces the const char[] size by while decaying it to const char*. I am using clang do expand the template only instead of building it:

/usr/bin/clang++ -Xclang -ast-print -fsyntax-only test_debugger.cpp > main.exe

template< unsigned int N >
constexpr unsigned int length( char const (&)[N] )
{
  return N-1;
}

However, it is clearly not liking to receive a const char*:

test_debugger.cpp:25:20: error: no matching function for call to 'length'
    static_assert( length(myExpression) == 17, "not equal to test_debugger.cpp" );
                   ^~~~~~
test_debugger.cpp:17:24: note: candidate template ignored: could not match 'const char [N]' against 'const char *const'
constexpr unsigned int length( char const (&)[N] )
                       ^
1 error generated.

Is there a way I can pass the const char* string with length( char const (&)[N] )?

Evandro Coan
  • 8,560
  • 11
  • 83
  • 144
  • No, the pointer-decayed array does not have information about the array size anymore. It is lost at that point. Why do you need that? You can still find the length of the passed string in a constant expression by searching for its null-terminator as done in the example above. – walnut Dec 21 '19 at 01:20
  • @walnut I just tough it would be fun to do this kind of voodoo. – Evandro Coan Dec 21 '19 at 01:22
  • Nope, it's a one way street. Once it's decayed, there's no going back. Especially when it makes an intermediate pit-stop of being assigned to a "`const char* myExpression`". – Sam Varshavchik Dec 21 '19 at 01:23
  • @SamVarshavchik, If I could change this pit-stop from `const char* myExpression` to something like `const char myExpression[]`? – Evandro Coan Dec 21 '19 at 01:25
  • @user Then the array doesn't decay to a pointer and `myExpression` will actually be an array type with known length. So that will work. – walnut Dec 21 '19 at 01:26
  • Can I re-write my **`pathlast(const char * const path)`** and related functions so it does not decay to **`const char*`** until I say so, i.e., I use it on my **`length( char const (&)[N] )`** where it finally decays? – Evandro Coan Dec 21 '19 at 01:28
  • @user as long as you keep passing around the array as `char const (&)[N]` (with `N` a template parameter) and never assign it to a pointer inbetween, there won't be any array-to-pointer decay. As soon as you assign it to a pointer, either in the parameter or a local variable, the size information is lost. – walnut Dec 21 '19 at 01:32
  • 3
    It's not that the information is lost, it's that the _information was never there_. `"cppdebugger/test_debugger.cpp"` is a `const char[30]`, and `myExpression` is a pointer to the middle of it. There _is_ no `const char[17]` anywhere at any point to deduce from. – Mooing Duck Dec 21 '19 at 01:34
  • @MooingDuck, Mind-blowing this! Thanks for the insight! – Evandro Coan Dec 21 '19 at 01:37
  • @user: it is theoretically possible to rewrite `pathlast` and related to avoid pointer decay, but it would require those methods to _create_ the `const char[17]` and similar and pass it around, so there would be a performance penalty of copying the newly created arrays around. – Mooing Duck Dec 21 '19 at 01:42
  • Nevermind, you can't return an array from a function. You have to use `std::array`. – Mooing Duck Dec 21 '19 at 01:51
  • I still can't figure out how to make it work. http://coliru.stacked-crooked.com/a/6520e18ec7f90a0a And you can't expand strings passed as compile time constants. – Mooing Duck Dec 21 '19 at 01:57
  • From c++17 on, std::string_view can give you a lot of information, I think there are implementations of it in libraries like abseil that can be c++11, however I don't know if they are constexpr compatible in that case. They could give you some inspiration – JVApen Dec 21 '19 at 03:01
  • You can't un-decay a `const char*`, but what you can do is take the argument by reference, `const T& str`, and then, you can check whether `T` is a pointer type or an array type. – Brian Bi Dec 21 '19 at 03:11
  • @MooingDuck: gcc/clang has UDL extension to allow to transform literal string as custom type. – Jarod42 Dec 21 '19 at 08:50

2 Answers2

1

No, this is impossible. In array-to-pointer decay the type is adjusted from array of size N to pointer to. It is a many-to-one mapping that looses information about the size.

Templates can only differentiate based on types (and value categories), so the information about the array size will not be available to them anymore.

The size information may still be reproduced as the length of the contained null-terminated string (if those are assumed to be equal), e.g. in a constexpr function by searching for the pointed-to-string's null-terminator. This constant value could then be used in place of the original size of the array in a template argument or similar value-dependent type, producing again a type that differentiates on the size.

But that is not working in the way you want to call length, since there is no template argument or the like involved and you are also not returning a pointer to the beginning of the array of the string literal from pathlast.

walnut
  • 21,629
  • 4
  • 23
  • 59
1

gcc/clang have an extension to allow to build UDL from literal string:

template<typename Char, Char... Cs>
struct CsHelper
{
    static constexpr const Char s[] = {Cs..., 0}; // as c-string
};

// That template uses the extension
template<typename Char, Char... Cs>
constexpr auto operator"" _cs() -> CsHelper<Char, Cs...> {
    return {};
}

See my answer from String-interning at compiletime for profiling to have MAKE_STRING macro if you cannot used the extension (Really more verbose, and hard coded limit for accepted string length).

Then you might do:

template<typename Char, Char... Cs, std::size_t ... Is>
constexpr auto findlastslash(CsHelper<Char, Cs...>, std::index_sequence<Is...>)
-> std::integral_constant<std::size_t, std::max({((Cs == '/') * (Is + 1))...})>
{
    return {};
}

template<typename Char, Char... Cs>
constexpr auto findlastslash(CsHelper<Char, Cs...> cs)
{
    return findlastslash(cs, std::make_index_sequence<sizeof...(Cs)>());
}

template<std::size_t Offset, typename Char, Char... Cs, std::size_t ... Is>
constexpr auto substring(CsHelper<Char, Cs...> cs, std::index_sequence<Is...>)
-> CsHelper<Char, std::get<Offset + Is>(std::make_tuple(std::integral_constant<Char, Cs>{}...))...>
{
    return {};
}

template<std::size_t Offset, typename Char, Char... Cs>
constexpr auto substring(CsHelper<Char, Cs...> cs)
{
    return substring<Offset>(cs, std::make_index_sequence<sizeof...(Cs) - Offset>());
}

template<typename Char, Char... Cs>
constexpr auto pathlast(CsHelper<Char, Cs...> cs)
{
    return substring<findlastslash(cs)>(cs);
}

template<std::size_t N >
constexpr std::size_t length(const char (&)[N])
{
    return N-1;
}

int main()
{
    constexpr auto myExpression = pathlast( "cppdebugger/test_debugger.cpp"_cs );
    std::cout << myExpression.s; // test_debugger.cpp
    STATIC_ASSERT( length(myExpression.s) == 17 );
}

Demo

Note: Demo is not in C++11, as std::index_sequence is C++14, but implementation for C++11 can be found.

Jarod42
  • 203,559
  • 14
  • 181
  • 302