C ++ Как я могу улучшить этот бит шаблонной метапрограммы, чтобы вернуть массив, включая размер? - PullRequest
4 голосов
/ 09 мая 2019

У меня есть утилита под названием choose_literal, которая выбирает буквенную строку, закодированную как char*, wchar_*, char8_t*, char16_t*, char32_t*, в зависимости от желаемого типа (выбор).

Это выглядит так:

    template <typename T>
    constexpr auto choose_literal(const char * psz, const wchar_t * wsz, const CHAR8_T * u8z, const char16_t * u16z, const char32_t * u32z) {
        if constexpr (std::is_same_v<T, char>)
            return psz;
        if constexpr (std::is_same_v<T, wchar_t>)
            return wsz;
    #ifdef char8_t
        if constexpr (std::is_same_v<T, char8_t>)
            return u8z;
    #endif
        if constexpr (std::is_same_v<T, char16_t>)
            return u16z;
        if constexpr (std::is_same_v<T, char32_t>)
            return u32z;
    }

Я предоставляю небольшой макрос препроцессора, чтобы сделать эту работу без необходимости вручную вводить каждую из этих кодировок:

// generates the appropriate character literal using preprocessor voodoo
// usage: LITERAL(char-type, "literal text")
#define LITERAL(T,x) details::choose_literal<T>(x, L##x, u8##x, u##x, U##x)

Это, конечно, работает только для литеральных строк, которые могут быть закодированы компилятором в целевом формате, но может быть что-то вроде пустой строки, как и символы ASCII (например, az, 0-9 и т. Д., Которые имеют представления). во всех этих кодировках).

например. вот тривиальный бит кода, который вернёт правильную пустую строку, если указан правильный тип символа 'T':

template <typename T>
constexpr const T * GetBlank() {
    return LITERAL(T, "");
}

Это замечательно, насколько это возможно, и это работает достаточно хорошо в моем коде.

Что я хотел бы сделать, так это изменить его так, чтобы я возвращал массив символов, включая его размер, как если бы я написал что-то вроде:

const char blank[] = "";

или

const wchar_t blank[] = L"";

Что позволяет компилятору знать длину строкового литерала, а не только его адрес.

My choose_literal<T>(str) возвращает только const T *, а не const T (&)[size], что было бы идеально.

В общем, я бы хотел иметь возможность передавать такие объекты без изменений, а не превращать их в один указатель.

Но в этом конкретном случае есть метод, на который вы могли бы указать мне, который позволяет мне объявлять структуру с членом-членом для желаемой кодировки, которая также знает длину своего массива?

Ответы [ 2 ]

4 голосов
/ 09 мая 2019

Немного магии рекурсии constexpr позволяет вам вернуть string_view соответствующего типа.

#include <string_view>
#include <type_traits>
#include <iostream>

template <typename T, class Choice, std::size_t N, class...Rest>
constexpr auto choose_literal(Choice(& choice)[N], Rest&...rest)
{
    using const_char_type = Choice;

    using char_type = std::remove_const_t<const_char_type>;

    if constexpr (std::is_same_v<T, char_type>)
    {
        constexpr auto extent = N;
        return std::basic_string_view<char_type>(choice, extent - 1);
    }
    else
    {
        return choose_literal<T>(rest...);
    }
}

int main()
{
    auto clit = choose_literal<char>("hello", L"hello");
    std::cout << clit;

    auto wclit = choose_literal<wchar_t>("hello", L"hello");
    std::wcout << wclit;
}

https://godbolt.org/z/4roZ_O

Если бы это был я, я бы, вероятно, хотел бы обернуть эту и другие функции в класс constexpr, который предлагает общие службы, такие как печать литерала в правильной форме в зависимости от типа потока и создание правильного типа строки из буквальный.

Например:

#include <string_view>
#include <type_traits>
#include <iostream>
#include <tuple>

template <typename T, class Choice, std::size_t N, class...Rest>
constexpr auto choose_literal(Choice(& choice)[N], Rest&...rest)
{
    using const_char_type = Choice;

    using char_type = std::remove_const_t<const_char_type>;

    if constexpr (std::is_same_v<T, char_type>)
    {
        constexpr auto extent = N;
        return std::basic_string_view<char_type>(choice, extent - 1);
    }
    else
    {
        return choose_literal<T>(rest...);
    }
}

template<class...Choices>
struct literal_chooser
{
    constexpr literal_chooser(Choices&...choices)
    : choices_(choices...)
    {}

    template<class T>
    constexpr auto choose() 
    {
        auto invoker = [](auto&...choices)
        {
            return choose_literal<T>(choices...);
        }; 

        return std::apply(invoker, choices_);
    }

    std::tuple<Choices&...> choices_;
};

template<class Char, class...Choices>
std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& os, literal_chooser<Choices...> chooser)
{
    return os << chooser.template choose<Char>();
}

template<class Char, class...Choices>
std::basic_string<Char> to_string(literal_chooser<Choices...> chooser)
{
    auto sview = chooser.template choose<Char>();
    return std::basic_string<Char>(sview.data(), sview.size());
}


int main()
{
    auto lit = literal_chooser("hello", L"hello");

    std::cout << lit << std::endl;
    std::wcout << lit << std::endl;

    auto s1 = to_string<char>(lit);
    auto s2 = to_string<wchar_t>(lit);

    std::cout << s1 << std::endl;
    std::wcout << s2 << std::endl;   
}

Важно использовать ссылочный тип аргумента Choices&. Строковые литералы C ++ являются ссылками на массивы const Char. Передача по значению приведет к тому, что литерал будет преобразован в указатель, что приведет к потере информации о размерах массива.

мы можем добавить другие сервисы, написанные в терминах literal_chooser:

template<class Char, class...Choices>
constexpr std::size_t size(literal_chooser<Choices...> chooser)
{
    auto sview = chooser.template choose<Char>();
    return sview.size();
}
2 голосов
/ 09 мая 2019

Мы собираемся изменить функцию так, чтобы она принимала const T (&)[size] для каждого входа, а тип возвращаемого значения будет decltype(auto). Использование decltype(auto) предотвращает превращение возврата в значение, сохраняя такие вещи, как ссылки на массивы.

Обновленная функция:

template <typename T, size_t N1, size_t N2, size_t N3, size_t N4>
constexpr decltype(auto) choose_literal(const char (&psz)[N1], const wchar_t (&wsz)[N2], const char16_t (&u16z)[N3], const char32_t (&u32z)[N4]) {
    if constexpr (std::is_same<T, char>())
        return psz;
    if constexpr (std::is_same<T, wchar_t>())
        return wsz;
    if constexpr (std::is_same<T, char16_t>())
        return u16z;
    if constexpr (std::is_same<T, char32_t>())
        return u32z;
}

В main мы можем присвоить результат чему-то типа auto&&:

#define LITERAL(T,x) choose_literal<T>(x, L##x,  u##x, U##x)

int main() {
    constexpr auto&& literal = LITERAL(char, "hello");  
    return sizeof(literal); // Returns 6
}

Потенциальное упрощение

Мы можем упростить функцию choose_literal, сделав ее рекурсивной, чтобы ее можно было расширить для любого числа типов. Это работает без каких-либо изменений в макросе LITERAL.

template<class T, class Char, size_t N, class... Rest>
constexpr decltype(auto) choose_literal(const Char(&result)[N], Rest const&... rest) {
    if constexpr(std::is_same_v<T, Char>)
        return result; 
    else
        return choose_literal<T>(rest...);
}
...