C++模板元编程(版本小于 17,最好是 11)
C++ template metaprogramming (version less than 17, preferably 11)
我有一些C++和标准库的经验,甚至写了几个简单的模板化函数,但是 - 像许多其他人一样,似乎 - 模板元编程让我的大脑感到困惑。
我想生成一个模板化函数,该函数解析一小部分合法值的 std::string。它需要根据字符串中找到的内容返回不同的类型,如果字符串与任何合法形式不匹配,则需要返回错误代码。例如,有效字符串可以是:
- a( 数字单值,例如"42">
- b( 单个非数字字符串,例如"任何内容">
- c( 逗号分隔的列表 a( 例如 "13,42,666">
- d( 逗号分隔的列表 b( 例如"任何东西,你,想要">
不同的实例化会"期望"某种类型并在失败的匹配上产生错误,例如,如果我想要 c( 并找到一个其他有效的 d( 那么:错误,反之亦然。显然,当找到有效的匹配项时,每个匹配项的返回类型将不同。
我认为返回值应该是 T 所在的pair<uint32_t errorcode,T>
:
- a(
uint32_t
- b(
std::string
- c(
std::vector<uint32_t>
- d(
std::vector<std::string>
在最高抽象级别,我想做的是:
实例化 1:
if(期望数字 &&(数字或list_of_numbers(( f( n | n1,n2,n3...( 否则错误
实例 2:
if(expecting string && (string or list_of_strings(( f( s | s1,s2,s3...( 否则错误
我想我需要#include <type_traits>
,可能需要 std::is_arithmetic但我只是无法"获取"根据预期类型是否为数字来获取模板"分支"的概念和语法。
更具体地说,我从这样的东西开始:
template<typename ReturnType, typename Expected>
pair<uint32_t,ReturnType> parseInput(std::string input,std::function<void(ReturnType)> f){
if(!initalvalidation(input)) return std::make_pair<999, ? >; // Exclude total garbage... ? = don't care as long as its a valid ReturnType
/*compile-time-if ?? */ *if* is_numeric(Expected){
ReturnType result;
result=parseInputForNumbers(input); // will return an empty ReturnType if invalid
return make_pair<(result.size() > 0 ? 0:123),result>; // 123 = bad numeric format
// obvs that won't work for uint32_t as it has no size() function, so again...HOW?
}
else {
... similar for strings
}
}
我知道它看起来不会像 if/else 块,但这是我唯一可以解释它的方法!
最后,我不需要解析方面的帮助,如何在运行时确定某些东西是否是数字等。我只想要有关模板函数结构/语法的帮助和建议。
我真的看不出模板在这里的意义:
其他一些想法怎么样?
您可能有:
enum class Error; // Or dedicated class or use std::optional
std::variant<std::uint32_t, Error> ParseUInt32(const std::string& s);
std::variant<std::string, Error> ParseString(const std::string& s);
std::variant<std::vector<std::uint32_t>, Error> ParseUInt32s(const std::string& s);
std::variant<std::vector<std::string>, Error> ParseStrings(const std::string& s);
如果要使用相同的名称来简化模板代码,则可以添加重载:
template <typename T> struct Tag {};
auto Parse(Tag<std::uint32>, const std::string& s) { return ParseUInt32(s); }
auto Parse(Tag<std::string>, const std::string& s) { return ParseString(s); }
auto Parse(Tag<std::vector<std::uint32_t>>, const std::string& s) { return ParseUInt32s(s); }
auto Parse(Tag<std::vector<std::string>>, const std::string& s) { return ParseStrings(s); }
因此,您可能有一个"通用"的:
std::variant<std::uint32_t,
std::string,
std::vector<std::uint32_t>,
std::vector<std::string>,
Error>
Parse(const std::string& s)
{
auto v1 = ParseUInt32s(s);
if (auto* res = std::get_if<std::vector<std::uint32_t>>(v1)) {
return std::move(*res);
}
auto v2 = ParseStrings(s);
if (auto* res = std::get_if<std::vector<std::string>>(v2)) {
return std::move(*res);
}
auto v3 = ParseUInt32(s);
if (auto* res = std::get_if<std::uint32_t>(v3)) {
return *res;
}
auto v4 = ParseString(s);
if (auto* res = std::get_if<std::string>(v4)) {
return std::move(*res);
}
return Error::InvalidInput;
}
注意:对于 C++11,Boost 相当于std::variant
/std::optional
。
你可以这样使用它:
template<typename ReturnType, typename Expected>
pair<uint32_t,ReturnType> parseInput(std::string input,std::function<void(ReturnType)> f){
// ...
if (std::is_arithmetic<Expected>::value){
// ...
} else {
// ...
}
}
基于Expected
和特征,编译器将始终知道它必须用于给定Expected
的if
的哪个分支,因此在生成的代码中不会有任何分支。
如果你想自己实现这个特征,它看起来像这样:
template<typename T>
struct is_numeric {
static const bool value = false; // defaults to false for each type you do not specialize
};
// define it as true for an int
template<>
struct is_numeric<int> {
static const bool value = true;
};
// define it as true for an float
template<>
struct is_numeric<float> {
static const bool value = true;
};
看看这个神螺栓,你可以看到gcc
即使对于-O0
优化了if子句:
#include <iostream>
template<typename T>
struct is_numeric {
static const bool value = false;
};
template<>
struct is_numeric<int> {
static const bool value = true;
};
int main()
{
if( is_numeric<int>::value ) {
std::cout << "int is_numeric " << std::endl;
}
if( is_numeric<char>::value ) {
std::cout << "char is_numeric " << std::endl;
}
return 0;
}
结果为:
.LC0:
.string "int is_numeric "
main:
push rbp
mov rbp, rsp
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
mov eax, 0
pop rbp
ret
__static_initialization_and_destruction_0(int, int):
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 1
jne .L5
cmp DWORD PTR [rbp-8], 65535
jne .L5
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
call __cxa_atexit
.L5:
nop
leave
ret
_GLOBAL__sub_I_main:
push rbp
mov rbp, rsp
mov esi, 65535
mov edi, 1
call __static_initialization_and_destruction_0(int, int)
pop rbp
ret
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- 有一个打印语句的函数是一种糟糕的编程实践吗
- 我是C++编程的新手,这些代码之间有什么区别,我应该使用哪一个
- 从值小于256的uint16到uint8的Endian安全转换
- 模板元编程:如何将参数包组合成新的参数包
- Qt Q串行端口未编程设备未关闭
- 将stl字符串缩小到小于15个字符的容量
- 如何检查两个 std::向量在小于 O(n) 的时间复杂度内是否相等
- 模板元编程 - 尝试实现维度分析
- 我是编程新手
- 为什么我只能在C++中使用可变长度数组分配小于 10 mb 的内存?
- C++编程从外部文本文件定义数组大小
- 了解算法的性能差异(如果以不同的编程语言实现)
- 使用 Gtkmm 以编程方式选择 Gtk::TextView 中的文本
- 如何为字符串生成唯一但一致的 N 位哈希(小于 64 位)?
- 如何将可变参数模板转换为多个单个模板?(C++竞争编程调试模板)
- 使用命名空间正确编程
- C++编程:运算符重载中的引用如何工作?
- Arduino 模块化编程与全局和设置
- C++模板元编程(版本小于 17,最好是 11)