Reputation: 9466
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:
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!" );
^
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:
C++11/14 strings for direct use in template parameter lists, template metaprogramming
.A Compile time PCRE (almost) compatible regular expression matcher.
Upvotes: 2
Views: 3088
Reputation: 217810
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!" );
}
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 >();
}
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
.
Upvotes: 4
Reputation: 9466
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!" );
}
Upvotes: 0