C++ 我怎样才能改进这个模板元程序来返回包含大小的数组?
C++ How can I improve this bit of template meta-program to give back the array including the size?
我有一个名为 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;
}
我提供了一个小的预处理器宏来完成这项工作w/o必须手动输入每个字符串编码:
// 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 等)也可以在所有这些编码中都有表示。
例如这是一段简单的代码,它将 return 给定有效字符类型 'T':
的正确空字符串
template <typename T>
constexpr const T * GetBlank() {
return LITERAL(T, "");
}
就目前而言,这已经很棒了,而且在我的代码中运行得很好。
我想做的是重构这个,这样我就可以取回字符数组,包括它的大小,就好像我写了这样的东西:
const char blank[] = "";
或
const wchar_t blank[] = L"";
这允许编译器知道字符串文字的长度,而不仅仅是它的地址。
我的 choose_literal<T>(str)
return 只有 const T *
而不是理想的 const T (&)[size]
。
总的来说,我希望能够完整地传递这些实体——而不是让它们变成一个指针。
但是在这种特定情况下,您是否可以向我指出一种技术,它允许我为所需的编码声明一个具有数据成员的结构,然后它也知道它的数组长度?
我们将更改函数,以便它为每个输入接受一个 const T (&)[size]
,并且 return 类型将是 decltype(auto)
。使用 decltype(auto)
可防止 return 衰减为一个值,从而保留对数组的引用等内容。
更新函数:
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...);
}
一点点 constexpr 递归魔法可以让你 return 一个 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;
}
如果是我,我可能想将这个函数和其他函数包装到一个 constexpr class 中,它提供常见的服务,例如根据流类型以正确的形式打印文字,并创建正确的字符串类型。
例如:
#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();
}
我有一个名为 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;
}
我提供了一个小的预处理器宏来完成这项工作w/o必须手动输入每个字符串编码:
// 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 等)也可以在所有这些编码中都有表示。
例如这是一段简单的代码,它将 return 给定有效字符类型 'T':
的正确空字符串template <typename T>
constexpr const T * GetBlank() {
return LITERAL(T, "");
}
就目前而言,这已经很棒了,而且在我的代码中运行得很好。
我想做的是重构这个,这样我就可以取回字符数组,包括它的大小,就好像我写了这样的东西:
const char blank[] = "";
或
const wchar_t blank[] = L"";
这允许编译器知道字符串文字的长度,而不仅仅是它的地址。
我的 choose_literal<T>(str)
return 只有 const T *
而不是理想的 const T (&)[size]
。
总的来说,我希望能够完整地传递这些实体——而不是让它们变成一个指针。
但是在这种特定情况下,您是否可以向我指出一种技术,它允许我为所需的编码声明一个具有数据成员的结构,然后它也知道它的数组长度?
我们将更改函数,以便它为每个输入接受一个 const T (&)[size]
,并且 return 类型将是 decltype(auto)
。使用 decltype(auto)
可防止 return 衰减为一个值,从而保留对数组的引用等内容。
更新函数:
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...);
}
一点点 constexpr 递归魔法可以让你 return 一个 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;
}
如果是我,我可能想将这个函数和其他函数包装到一个 constexpr class 中,它提供常见的服务,例如根据流类型以正确的形式打印文字,并创建正确的字符串类型。
例如:
#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();
}