我使用以下界面制作了一堆UTF转换函数:
template <typename T, typename U> std::basic_string<T> UTFConvert(std::basic_string_view<U> a_String);
char
,char16_t
和char32_t
的所有组合都有实现。但现在我还需要添加对wchar_t
的支持。我在编译时知道wchar_t
的大小,所以理论上我可以用相同大小的字符调用函数。
问题是我必须将结果字符串复制回std::wstring
。例如,如果sizeof(wchar_t) == 2
我最终会做这样的事情:
template <typename T, typename U>
std::enable_if_t<std::is_same_v<T, wchar_t>, std::basic_string<T>> UTFConvert(std::basic_string_view<U> a_String)
{
const std::u16string utf16 = UTFConvert<char16_t>(a_String);
std::wstring wstr;
wstr.resize(utf16.size());
memcpy(wstr.data(), utf16.data(), utf16.size() * sizeof(wchar_t));
return wstr;
}
像这样复制字符串似乎有点浪费。有没有办法避免这种情况,没有为不同类型重复实现相同的代码两次?
答案 0 :(得分:2)
通过专门设置T大小而不是特定字符类型的函数来解决问题:
template <typename T, typename U>
std::enable_if_t<sizeof(T) == 2, std::basic_string<T>> UTFConvert(std::basic_string_view<U> a_String);
只需在那里撒上一些static_assert
以获得理智,一切都很完美!
感谢@MassimilianoJanes的建议。
答案 1 :(得分:0)
你处于C ++的一角,标准有一些粗糙的地方。
这里有一些理论上的缺陷。
第一个问题是在C ++中取消引用wchar_t*
缓冲区上的char16_t
是不合法的,反之亦然。此问题称为&#34;严格别名&#34;。
如果你有一个固定大小的缓冲区,可以通过仔细的来回复制和构造来解决这个问题。但是,C ++标准中存在一个缺陷,即创建动态大小的数组无法实现&#34;手动&#34;没有在问题类型上调用new[]
(根据用户代码的标准,实现std::vector
或类似情况是不可能的。)
这是标准中的一个缺陷,但据我所知,目前还没有解决。
所以问题就变成了,你想要遵循标准的程度如何,以及你想要的东西能用多少呢?
我最接近符合标准的代码来解决您的问题是:
编写一个新的UTFConvert_to_sink
函数。
template<class T>
struct tag_t {};
template<class CharType, class Sink>
void UTFConvert_to_sink(std::basic_string_view<CharType> from, tag_t<CharType> to, Sink&& sink) {
for (CharType c : from)
sink(c);
}
template<class Sink>
void UTFConvert_to_sink(std::basic_string_view<char> from, tag_t<std::char16_t> to, Sink&& sink); // TODO
template<class Sink>
void UTFConvert_to_sink(std::basic_string_view<char> from, tag_t<std::char32_t> to, Sink&& sink); // TODO
template<class Sink>
void UTFConvert_to_sink(std::basic_string_view<char16_t> from, tag_t<char> to, Sink&& sink); // TODO
template<class Sink>
void UTFConvert_to_sink(std::basic_string_view<char16_t> from, tag_t<std::char32_t> to, Sink&& sink); // TODO
template<class Sink>
void UTFConvert_to_sink(std::basic_string_view<char32_t> from, tag_t<char> to, Sink&& sink); // TODO
template<class Sink>
void UTFConvert_to_sink(std::basic_string_view<char32_t> from, tag_t<std::char16_t> to, Sink&& sink); // TODO
请注意,这些仅在Sink
上模板化。 Sink
的工作原理应该从我的模板中清楚地表达出来&#34;同样的#34;
UTFConvert
可以写在上面,如下所示:
template<class To, class From>
std::basic_string<To> UTFConvert( std::basic_string_view<From> from ) {
std::basic_string<To> retval;
UTFConvert_to_sink( from, tag_t<To>{}, [&retval]( To c ) {retval.push_back(c);} );
}
处理所有相关类型。
现在剩下的是wchar_t
中的UTFConvert_to_sink
。
using char_type_same_size_as_wchar_t = std::char16_t; // or char32_t depending on platform.
template<class From, class Sink>
void UTFConvert_to_sink(std::basic_string_view<From> from, tag_t<wchar_t> to, Sink&& sink) {
UTFConvert_to_sink( from, tag_t<char_type_same_size_as_wchar_t>{}, [&sink](auto c) {
wchar_t wc = c;
sink( wc );
});
}
我认为一切都是标准明智的。并且wchar_t函数应该编译为几乎没有。
如果您想要支持来自wchar_t
,由于标准缺陷而无法在不调用new T[]
的情况下创建数组,事情会变得混乱。我们可以在清洗每个元素的地方靠近。
template<class U, class T>
U* landry_pod( T* in ) {
static_assert( sizeof(T)==sizeof(U) );
static_assert( std::is_trivially_copyable<T>{} && std::is_trivially_copyable<U>{} );
char buff[sizeof(T)];
std::memcpy( buff, in, sizeof(T) );
U* r = ::new( (void*)in ) U;
std::memcpy( r, buff, sizeof(U) );
return r;
}
landry_pod<OutType>
是一个有趣的函数,因为它编译为零指令(尝试它),但它是一种合法的方法将指针转换为类型为T的简单可复制对象并获得指向一个简单的指针包含完全相同字节的相同大小U的可复制对象。
所以我最接近的是依次遍历basic_string_view<wchar_t>
,laundry_pod
每个元素,然后选择指针并用它们创建basic_string_view<char16_t>
,然后将它们提供给{ {1}}。
现在,所有这些都是荒谬的体操来解决标准中严格的别名规则,它甚至不足以实际生成完全定义的行为。
请注意,我写了UTFConvert_to_sink
个字符;写一个更高级的(即,与字符分开一定长度,和/或允许你按顺序提供)也可以。