Home > OS >  Use std::string_view size in template parameter from NTTP constructor
Use std::string_view size in template parameter from NTTP constructor

Time:11-18

I have a simple C 20 compile-time string type that is defined like this:

template<std::size_t N>
class compile_time_string_storage
{
  public:
    constexpr compile_time_string_storage(std::array<char, N> str) noexcept
      : value(str)
    {
    }

    constexpr compile_time_string_storage(const char (&str)[N]) noexcept
    {
        std::copy_n(str, N, value.begin());
    }

    constexpr compile_time_string_storage(char c) noexcept
      : value{ c }
    {
    }

    std::array<char, N> value;
};

compile_time_string_storage(char)->compile_time_string_storage<1>;

template<compile_time_string_storage S>
class str
{
  public:
    [[nodiscard]] constexpr static std::string_view get() noexcept
    {
        return { S.value.data(), size_ };
    }

    [[nodiscard]] constexpr static std::size_t size() noexcept { return size_; }

  private:
    constexpr static std::size_t calculate_size() noexcept
    {
        auto count = std::size_t{ 0 };
        for (; count < S.value.size();   count) {
            if (S.value[count] == '\0') {
                break;
            }
        }
        return count;
    }

    constexpr static std::size_t size_ = calculate_size();
};

This works. However I would like to add support of construction via std::string_view, which in principle should be straightforward as it can be constexpr-constructed, but I'm struggling.

Initially I added a constexpr constructor overload to compile_time_string_storage with a deduction guide:

<source>:55:80: error: non-type template argument is not a constant expression
compile_time_string_storage(std::string_view str)->compile_time_string_storage<str.size()>;
                                                                               ^~~~~~~~~~

The constructor argument can't be used in a compile-time context. I then tried using the deduction guide as 'tag' I can specialise on:

constexpr auto string_view_tag = std::numeric_limits<std::size_t>::max();

template<>
class compile_time_string_storage<string_view_tag>
{
public:
    constexpr compile_time_string_storage(std::string_view str) : value(str)
    {}

    std::string_view value;
};

compile_time_string_storage(std::string_view)->compile_time_string_storage<string_view_tag>;

Nope, can't use that:

<source>:52:22: note: 'compile_time_string_storage<string_view_tag>' is not a structural type because it has a non-static data member of non-structural type 'std::string_view' (aka 'basic_string_view<char>')
    std::string_view value;
                     ^
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c  /11.2.0/string_view:510:18: note: 'std::string_view' (aka 'basic_string_view<char>') is not a structural type because it has a non-static data member that is not public
      size_t        _M_len;
                    ^

OK, what if I make an equivalent 'structural' version of std::string_view?

struct str_view
{
    constexpr str_view(std::string_view str) : data_{str.data()}, size_{str.size()}
    {}

    constexpr const char* const data() const noexcept { return data_; }
    constexpr std::size_t size() const noexcept { return size_; }

    const char* data_;
    std::size_t size_;
};

template<>
class compile_time_string_storage<string_view_tag>
{
public:
    constexpr compile_time_string_storage(std::string_view str) : value(str)
    {}

    str_view value;
};

Nope.

<source>:88:23: error: pointer to subobject of string literal is not allowed in a template argument
    static_assert(str<"hello"sv>::get() == "hello");
                      ^

Is there a way of using a std::string_view to initialise compile_time_string_storage?

I'm currently using the deduction guide to set a fixed value for N (set by a compiler define), which works in the sense that the compiler is smart enough to just keep the original string data in static storage rather than the std::array contents. But it's hack as you need to know the maximum string length you'll need up front, and it's just not right.

CodePudding user response:

Is there a way of using a std::string_view to initialise compile_time_string_storage?

Your problem is that you seem to want a number of things that cannot all go together. You want:

  1. To create a compile_time_string_storage from a constexpr string_view without specifying the size explicitly as a template parameter.
  2. To have the compile_time_string_storage provide an array of N characters of storage for the string.

You cannot have both. So long as compile_time_string_storage has a size template parameter that must be filled in by a property of a non-template parameter, this will not work. Function parameters, and therefore members of them, are not constexpr.

Your last attempt drops #2, and that approach can work. However, you ran into a separate problem: pointers to string literals cannot be used as template parameters. You cannot smuggle them into template parameters as members of some other object either.

It would have worked if you used a stack array of strings as your source for the string_view. Of course, then your "compile_time_string_storage` would be a lie, because it would not actually store the string. It would be referencing existing storage that may or may not exist by the time someone uses the string.

If your ultimate goal requires the ability to shove a string literal pointer into this class without copying characters, give it up. The standard expressly forbids it. So you're left with having the string store an array.

An array whose size cannot come from a function parameter.

So your only recourse is to do it manually: take the constexpr string_view, get its size, and pass that size explicitly as a template parameter:

constexpr compile_time_string_storage<some_constexpr_string_view.size()> var(some_constexpr_string_view);

It's ugly, but that's your only option.

Alternatively, avoid string_view altogether and use a span<char const, N> constructed from a string literal. span is able to maintain the size of the span as a template parameter, which in turn allows your compile_time_string_storage to extract it without having to manually insert the template argument.

  • Related