1

I've been using Boost.Hana to generate compile-time strings for use as template parameters with gcc:

using namespace boost::hana::literals;
#define GQL_STR(tok) decltype(#tok ## _s)

It relies on having the BOOST_HANA_CONFIG_ENABLE_STRING_UDL define set. However my code is supposed to be portable and this relies on a gcc/clang extension. After reading this great answer, I wrote some code that mimics it using Boost.Mp11 for little extra ease:

#include <boost/mp11/list.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/hana/string.hpp>
#include <boost/preprocessor/config/limits.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>

#include <iostream>

namespace
{
template <int N>
constexpr char get(const char(&str)[N], int i)
{
    return i < N ? str[i] : '\0';
}

struct is_null_char
{
    template <typename T>
    using fn = std::is_same<std::integral_constant<char, '\0'>, T>;
};

template <char... Cs>
constexpr auto list_to_string(boost::mp11::mp_list_c<char, Cs...>)
{
    return boost::hana::string<Cs...>{};
}

template <char... Cs>
struct builder
{
    using strip_null = boost::mp11::mp_remove_if_q<
        boost::mp11::mp_list_c<char, Cs...>,
        is_null_char
    >;

    using type = decltype(list_to_string(strip_null{}));
};

#define GQL_STR_CHAR(z, n, tok) \
    BOOST_PP_COMMA_IF(n) get(tok, n)

#define GQL_STR_N(n, tok) \
    typename builder<BOOST_PP_REPEAT(n, GQL_STR_CHAR, tok)>::type
}

#define GQL_STR(tok) GQL_STR_N(128, #tok)

int main()
{
    using hello_s = GQL_STR(hello);

    std::cout << hello_s{}.c_str() << std::endl;
    return EXIT_SUCCESS;
}

However I don't like the idea that we're forcing the compiler to generate 128 char template parameters, only to then force it to remove all the superfluous ones when we know the string size at compile time. So I made a shorter one:

namespace
{
template <int N, int... I>
constexpr auto gql_str_impl(const char(&str)[N], std::integer_sequence<int, I...>)
{
    return boost::hana::string<str[I]...>{};
}

template <int N>
constexpr auto gql_str(const char(&str)[N])
{
    return gql_str_impl(str, std::make_integer_sequence<int, N-1>{});
}
}

#define GQL_STR(tok) \
    decltype(gql_str(#tok))

This does not work. It does not work for a very good reason:

<source>:15:43: error: 'str' is not a constant expression
   15 |     return boost::hana::string<str[I]...>{};

Now what I don't understand, is why the first code sample works at all. The macro expands out to:

typename builder<get("hello", 0), get("hello", 1), ...>::type

Why the is str in the get function considered a constant expression here, but not in the second code sample?

cmannett85
  • 21,725
  • 8
  • 76
  • 119
  • It all boils down to the simple fact that a function parameter is never a constant expression. I'll try and find a dupe, I know there are a few. – NathanOliver Jun 25 '19 at 12:33
  • This is the closest one I can find: https://stackoverflow.com/questions/39236181/is-there-a-way-to-forward-argument-to-inner-constexpr-function – NathanOliver Jun 25 '19 at 12:39
  • @NathanOliver, ah so the first code sample _shouldn't_ work either, but presumably the compiler is doing something to allow it. – cmannett85 Jun 25 '19 at 12:49
  • The first code works because you don't try to use anything about `str` in a constant expression. `return i < N ? str[i] : '\0';` can be run at both compile and run time. OTOH `return boost::hana::string{};` can only be done at compile time but it is legal to call a constexpr function at run time so you are not allow to do this. – NathanOliver Jun 25 '19 at 12:52
  • But I do use it in a constant expression, it's elements are accessed to create a return value that's put into a template value parameter. – cmannett85 Jun 25 '19 at 12:58
  • But you aren't doing that inside the context of `get`. In `get` the code doesn't need compile time constants. It would be perfectly valid to call get at runtime. `gql_str_impl` is not the same. You are trying to use the function parameter inside the function as a template parameter. You can't do that though since `str` is no longer a compile time constant (even if it is created from one). You code basically boils down to something like `template void foo() { std::cout << i; } void foo(int i) { foo; }` where in the second function you just can't use `i` like that. – NathanOliver Jun 25 '19 at 13:04
  • Thank you, I understand now! – cmannett85 Jun 25 '19 at 13:08

0 Answers0