C 如何改进此模板元编程以将包括尺寸在内的数组回馈

C++ How can I improve this bit of template meta-program to give back the array including the size?

本文关键字:包括尺 数组 何改进 编程      更新时间:2023-10-16

我有一个名为 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字符一样(即A-Z,0-9等(可以像空字符串一样在所有这些编码中(。

例如。这是一个微不足道的代码,它将返回给定有效字符类型的" t":

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

,这很棒,而且在我的代码中效果很好。

我想做的是重构它,以便我恢复角色阵列,包括它的大小,好像我写了类似的东西:

const char blank[] = "";

const wchar_t blank[] = L"";

允许编译器知道字符串静态的长度,而不仅仅是其地址。

我的choose_literal<T>(str)仅返回const T *,而不是const T (&)[size],这是理想的。

一般而言,我希望能够完整地传递此类实体 - 而不是将它们放入指针。

但是,在这种特定情况下,您是否可以将我指向我,这使我能够声明带有数据成员的结构,以编码所需的编码,然后也知道其阵列长度?

一点点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数组的引用。通过价值传递将导致字面衰减到指针中,这将失去有关数组程度的信息。

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

我们将更改函数,以便为每个输入需要一个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;
}

主要,我们可以将结果分配给类型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...);
}