Когда массив символов является постоянным выражением?

Я использовал Boost.Hana для генерации строк времени компиляции для использования в качестве параметров шаблона с gcc:

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

Это зависит от наличия набора BOOST_HANA_CONFIG_ENABLE_STRING_UDL определения. Однако мой код должен быть переносимым, и это зависит от расширения gcc/clang. Прочитав этот замечательный ответ, я написал некоторый код, который имитирует его с помощью Boost.Mp11 для дополнительного удобства:

#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;
}

Однако мне не нравится идея, что мы заставляем компилятор генерировать 128-символьные параметры шаблона только для того, чтобы затем заставить его удалить все лишние, когда мы знаем размер строки во время компиляции. Поэтому я сделал короче:

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))

Это не работает. Не работает по очень веской причине:

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

Теперь я не понимаю, почему первый пример кода вообще работает. Макрос расширяется до:

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

Почему is str в функции get здесь считается константным выражением, а нет во втором примере кода?


person cmannett85    schedule 25.06.2019    source источник
comment
Все сводится к тому простому факту, что параметр функции никогда не является константным выражением. Я постараюсь найти обман, я знаю, что есть несколько.   -  person NathanOliver    schedule 25.06.2019
comment
Это самое близкое, что я могу найти: stackoverflow.com/questions/39236181/   -  person NathanOliver    schedule 25.06.2019
comment
@NathanOliver, а, значит, и первый образец кода не должен работать, но, по-видимому, компилятор что-то делает, чтобы это разрешить.   -  person cmannett85    schedule 25.06.2019
comment
Первый код работает, потому что вы не пытаетесь использовать что-либо о str в постоянном выражении. return i < N ? str[i] : '\0'; можно запускать как во время компиляции, так и во время выполнения. OTOH return boost::hana::string<str[I]...>{}; может выполняться только во время компиляции, но законно вызывать функцию constexpr во время выполнения, поэтому вам не разрешено это делать.   -  person NathanOliver    schedule 25.06.2019
comment
Но я использую его в постоянном выражении, доступ к его элементам осуществляется для создания возвращаемого значения, которое помещается в параметр значения шаблона.   -  person cmannett85    schedule 25.06.2019
comment
Но вы не делаете этого в контексте get. В get коду не нужны константы времени компиляции. Было бы совершенно правильно вызывать get во время выполнения. gql_str_impl не то же самое. Вы пытаетесь использовать параметр функции внутри функции в качестве параметра шаблона. Вы не можете этого сделать, поскольку str больше не является константой времени компиляции (даже если она создана из нее). Ваш код в основном сводится к чему-то вроде template <int i> void foo() { std::cout << i; } void foo(int i) { foo<i>; }, где во второй функции вы просто не можете использовать i вот так.   -  person NathanOliver    schedule 25.06.2019
comment
Спасибо, теперь понял!   -  person cmannett85    schedule 25.06.2019