C++模板元编程(版本小于 17,最好是 11)

C++ template metaprogramming (version less than 17, preferably 11)

本文关键字:小于 编程 版本 C++      更新时间:2023-10-16

我有一些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和特征,编译器将始终知道它必须用于给定Expectedif的哪个分支,因此在生成的代码中不会有任何分支。

如果你想自己实现这个特征,它看起来像这样:

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