跳到主要内容
左猫娘右猫娘

【C++】编译期函数指针唯一化

· 阅读需 6 分钟
Heng_Xin
ここから先は一方通行だ!

不知道起什么名字, 直接看要实现什么吧:

struct Wdf {
template <bool... Bs, typename... Ts>
std::size_t func(db::FieldPair<Ts>...) {
// ...
}
};
cpp

如何获取 &Wdf::func<Bs..., Ts...>?

Tip

此为期望效果, [&Wdf::func<Bs..., Ts...>] 语法是错误的! 因为 Ts... 无法被指定.

  • 环境: C++20

一、实际运用的场景

目的: 数据库的库都有 ? 占位以防止被SQL注入.

最近我编写的库是基于反射的. 因此会有以下代码:

template <
meta::FixedString... SqlBody,
typename... MemberPtr,
typename U = meta::GetMemberPtrsClassType<MemberPtr...>
>
internal::StmtCallChain& updateBy(FieldPair<MemberPtr>... fmPair) {
return getSqlCache(
meta::TypeId::make</* Type / NTTP */>(),
[&] {
auto sql = MakeSqlStr::makeUpdateSqlFragment<U, MemberPtr...>(fmPair.ptr...);
((sql += meta::ToCharPack<SqlBody>::view()), ...);
return internal::StmtCallChain{sql, _db};
}
).template bind<true>(fmPair.dataView...);
}
cpp

因为 SQL 都需要预编译一下, 然后之后我们再只需要指定 ? 的内容就好了:

class SQLiteStmt {
public:
SQLiteStmt(std::string_view sql, ::sqlite3* db)
: _stmt{nullptr}
{
// 预编译
if (::sqlite3_prepare_v2(
db, sql.data(), sql.size(),
&_stmt, nullptr) != SQLITE_OK
) [[unlikely]] {
throw std::runtime_error{/*...*/};
}
}

~SQLiteStmt() noexcept {
if (_stmt) [[likely]] {
::sqlite3_finalize(_stmt); // RAII了 预编译
}
}
private:
::sqlite3_stmt* _stmt;
};
cpp

观察上面代码你会发现, sql每次调用, 都需要预编译, 我们完全可以复用已经预编译的SQL啊, 不需要每次创建 (这也是正确并且期望的做法吧qwq)

故有之前的代码, 其中getSqlCache就是实现复用. 其中typeId就是根据 updateBy 等方法唯一生成的!

class SQLiteDB {
template <typename InitFunc>
requires (std::is_same_v<std::invoke_result_t<InitFunc>, internal::StmtCallChain>)
internal::StmtCallChain& getSqlCache(std::size_t typeId, InitFunc&& init) {
auto it = _sqlCache.find(typeId);
if (it == _sqlCache.end()) [[unlikely]] {
auto [jt, ok] = _sqlCache.emplace(typeId, init());
if (!ok) [[unlikely]] {
throw std::runtime_error{"getSqlCache: emplace err"};
}
return jt->second;
}
return it->second;
}
private:
::sqlite3* _db{};
std::map<std::size_t, internal::StmtCallChain> _sqlCache{}; // 缓存它
};
cpp

二、问题

Note

而因为API的设计, 我们需要定义一些语句作为编译期NTTP传入, 而又因为并没有实现 + 运算符, 我灵活的使用了可变参数替代.

template <
meta::FixedString... SqlBody,
typename... MemberPtr,
typename U = meta::GetMemberPtrsClassType<MemberPtr...>
internal::StmtCallChain& updateBy(FieldPair<MemberPtr>... fmPair) { /* */ }
cpp

简单使用如下:

回到问题, 我们现在需要一个可以唯一化方法的东西. 我们不能直接指定参数, 因为可能会有其他参数和我们的冲突.

也不好混合的指定 auto... + Ts..., 因为这需要严格的规范才可以防止不出错. 并且还需要顺序, 毕竟不能 <NTTP, T, NTTP', T'> 这样交替指定. 而且这样使用 Ts... 一般得作为函数参数推导才行. 总之就是麻烦.

此处就有很简单的方法: 成员函数指针!, 他可以作为 <auto Ptr> NTTP 直接传参.

并且是 唯一 的. 也不怕和其他类的相同类型实例化模板搞混. 因为是 C++! 成员指针 T Class::*, 自带类名称签名.

目前就遇到问题:

struct Wdf {
template <bool... Bs>
std::size_t func() {
return meta::TypeId::make<&Wdf::func<Bs...>>(); // ok
}
};
cpp

但是

template <typename T>
struct Wrap {};

struct Wdf {
template <bool... Bs, typename... Ts>
std::size_t func(Wrap<Ts>...) {
return meta::TypeId::make<&Wdf::func<Bs..., Ts...>>(); // err
}
};
cpp

在有可变参数(Bs...)情况下, 第二个参数 Ts... 是无法通过模板指定的:

#include <string>

template <typename T>
struct Wrap {};

struct Wdf {
template <bool... Bs, typename... Ts>
std::size_t func(Wrap<Ts>...) {
return {};
}
};

int main() {
Wdf{}.func<false>(Wrap<int>{}); // ok
Wdf{}.func<false, int>({}); // err
return 0;
}
cpp
Compiler stderr

<source>: In function 'int main()':
<source>:15:27: error: no matching function for call to 'Wdf::func<false, int>(<brace-enclosed initializer list>)'
15 | Wdf{}.func<false, int>({}); // err
| ~~~~~~~~~~~~~~~~~~~~~~^~~~
<source>:15:27: note: there is 1 candidate
<source>:8:17: note: candidate 1: 'template<bool ...Bs, class ... Ts> std::size_t Wdf::func(Wrap<Ts>...)'
8 | std::size_t func(Wrap<Ts>...) {
| ^~~~
<source>:8:17: note: template argument deduction/substitution failed:
<source>:15:27: error: type/value mismatch at argument 1 in template parameter list for 'template<bool ...Bs, class ... Ts> std::size_t Wdf::func(Wrap<Ts>...)'
15 | Wdf{}.func<false, int>({}); // err
| ~~~~~~~~~~~~~~~~~~~~~~^~~~
<source>:15:27: note: expected a constant of type 'bool', got 'int'
cpp

三、解决方案

指定 成员函数指针类型, 我们知道函数指针是带有参数的: 返回值 (*)(参数)

所以我们可以在参数这里推导 Ts....

模板 <Bs...> 部分可以看作为函数名称的签名.

二合一, 就是唯一确定的了:

struct Wdf {
template <bool... Bs, typename... Ts>
std::size_t func(Wrap<Ts>...) {
using Ptr = std::size_t (Wdf::*)(db::FieldPair<Ts>...);
constexpr Ptr ptr = &Wdf::func<Bs...>;
return meta::TypeId::make<ptr>();
}
};
cpp

Tip

仅指定 meta::TypeId::make<&Wdf::func<Bs...>>() 而不指定 Ts... 是错误的哦!

它没有代表任何的 Ts... 类型到 TypeId 中!

说白了, 就是 TypeId(func<false>(Wrap<int>{})) == TypeId(func<false>(Wrap<double>{}))

请作者喝奶茶:
Alipay IconQR Code
Alipay IconQR Code
本文遵循 CC CC 4.0 BY-SA 版权协议, 转载请标明出处
Loading Comments...