【C++】编译期函数指针唯一化
不知道起什么名字, 直接看要实现什么吧:
struct Wdf {
template <bool... Bs, typename... Ts>
std::size_t func(db::FieldPair<Ts>...) {
// ...
}
};
如何获取 &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...);
}
因为 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;
};
观察上面代码你会发现, 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{}; // 缓存它
};
二、问题
Note
而因为API的设计, 我们需要定义一些语句作为编译期NTTP传入, 而又因为并没有实现 +
运算符, 我灵活的使用了可变参数替代.
template <
meta::FixedString... SqlBody,
typename... MemberPtr,
typename U = meta::GetMemberPtrsClassType<MemberPtr...>
internal::StmtCallChain& updateBy(FieldPair<MemberPtr>... fmPair) { /* */ }
简单使用如下:
回到问题, 我们现在需要一个可以唯一化方法的东西. 我们不能直接指定参数, 因为可能会有其他参数和我们的冲突.
也不好混合的指定 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
}
};
但是
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
}
};
在有可变参数(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;
}
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'
三、解决方案
指定 成员函数指针类型, 我们知道函数指针是带有参数的: 返回值 (*)(参数)
所以我们可以在参数这里推导 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>();
}
};
Tip
仅指定 meta::TypeId::make<&Wdf::func<Bs...>>()
而不指定 Ts...
是错误的哦!
它没有代表任何的 Ts...
类型到 TypeId 中!
说白了, 就是 TypeId(func<false>(Wrap<int>{})
) == TypeId(func<false>(Wrap<double>{})
)