Hen*_*ung 6 c++ string templates c++17
考虑以下 C++ 代码:
#include <iostream>
void get_val_from_db(const char* key, signed char& value)
{
std::cout << "Getting value from db with signed char!\n";
}
void get_val_from_db(const char* key, unsigned char& value)
{
std::cout << "Getting value from db with unsigned char!\n";
}
// overload to convert first argument from std::string to const char*
template <typename T>
void get_val_from_db(const std::string& key, T& value)
{
get_val_from_db(key.c_str(), value);
}
int main()
{
unsigned char my_value_unsigned = 0; // OK!
get_val_from_db("my key", my_value_unsigned);
signed char my_value_signed = 0; // OK!
get_val_from_db("my key", my_value_signed);
char my_value_char = 0; // NOK! Can't convert! Becomes recursive!
get_val_from_db("my key", my_value_char);
// Can't remove the std::string conversion altogether because these needs to keep working
std::string key = "my key";
get_val_from_db(key, my_value_signed);
get_val_from_db(key, my_value_unsigned);
}
Run Code Online (Sandbox Code Playgroud)
我有一个get_val_from_db接收键名称和键类型的函数,每种类型都有许多专门化,包括一个包罗万象的模板版本,它仅将键名称从 转换为std::string,const char*因为这些不能自动完成。
在编译器资源管理器上检查此代码的实时示例char,请注意,当调用作为类型传递的函数时,程序崩溃了。发生这种情况char是因为不可转换为signed char&(请参阅另一个编译器资源管理器示例),因此在传递时char,要调用的最佳可用函数是void get_val_from_db(const std::string& key, T& value)然后调用自身的函数,因为const char* 可以直接转换为std::string。因此调用变成递归并导致堆栈溢出。
我当然可以添加一个新的专业化char,但我想让代码对未来的开发人员来说更安全。我希望新的参数类型不被捕获void get_val_from_db(const std::string& key, T& value),而是得到一个漂亮而有趣的编译错误。我不想完全删除该函数,因为我们需要进行std::string转换const char*以保持当前代码正常工作。综上所述,如何防止这种从const char*到 的隐式转换的std::string发生?
template <typename T>
void get_val_from_db(const std::string& key, T& value)
{
// how to prevent key.c_str() from being converted to std::string?
get_val_from_db(key.c_str(), value);
}
Run Code Online (Sandbox Code Playgroud)
我有一个 C++ 17 编译器可供使用。
一般来说,有两种方法可以确保重载集中的函数不被选中:
您已经认识到可以通过添加以下内容来执行 1. 操作:
void get_val_from_db(const char* key, char& value)
{
std::cout << "Getting value from db with signed char!\n";
}
Run Code Online (Sandbox Code Playgroud)
这是一种有效的解决方案,但还有其他解决方案:
void get_val_from_db(const std::string& key, char&) = delete;
Run Code Online (Sandbox Code Playgroud)
如果调用此函数,您将收到编译器错误。与模板相比,它在重载解析方面会获胜,因为它是非模板。
C++17 允许您使用 SFINAE 约束函数。std::enable_if是一个常用工具:
template <typename T>
auto get_val_from_db(const std::string& key, T& value)
-> std::enable_if_t<!std::is_same_v<char, T>>
{
get_val_from_db(key.c_str(), value);
}
Run Code Online (Sandbox Code Playgroud)
通过 clang,我们可以得到非常好的诊断std::enable_if:
<source>:31:5: error: no matching function for call to 'get_val_from_db'
get_val_from_db("my key", my_value_char);
^~~~~~~~~~~~~~~
...
<source>:16:6: note: candidate template ignored: requirement '!std::is_same_v<char, char>' was not satisfied [with T = char]
auto get_val_from_db(const std::string& key, T& value)
^
Run Code Online (Sandbox Code Playgroud)
您还可以通过将“高级”功能与某些“详细”功能分开来解决此问题。我们可以让用户总是调用函数 template get_val_from_db,并且它将分派给较低级别的函数:
// note: prefer std::string_view over const char* or const std::string&
// (it can be converted from both)
void get_schar_from_db(std::string_view key, signed char& value)
{
std::cout << "Getting value from db with signed char!\n";
}
void get_uchar_from_db(std::string_view key, unsigned char& value)
{
std::cout << "Getting value from db with unsigned char!\n";
}
template <typename T>
void get_val_from_db(std::string_view key, T& value)
{
if constexpr (std::is_same_v<T, signed char>) {
return get_schar_from_db(key, value);
}
else if constexpr (std::is_same_v<T, unsigned char>) {
return get_uchar_from_db(key, value);
}
else {
// TODO: provide fallback case, dispatch to more special cases, etc.
}
}
Run Code Online (Sandbox Code Playgroud)
您还可以将其与上面的约束方法混合使用,并且只需将约束施加到向用户公开的顶级函数上。
我想要的是,如果我提供一个不专门用于其自身功能的参数,则会出现编译错误
然后,您可以在不存在重载的所有情况下, SFINAE取消函数模板的实例化:void get_val_from_db(const char*, T&)
// convert first argument from std::string to const char*
// only if "void get_val_from_db(const char*, T&)" exists
template <class T>
auto get_val_from_db(const std::string& key, T& value) ->
std::void_t<decltype(static_cast<void(*)(const char*, T&)>(get_val_from_db))>
{
get_val_from_db(key.c_str(), value);
}
Run Code Online (Sandbox Code Playgroud)
为了获得更清晰的错误消息,您可以创建一个类型特征来检查是否void get_val_from_db(const char*, T&)存在 的实现并使用 astatic_assert代替。
#include <utility>
// helper type trait
template <class T>
struct has_implementation {
static std::false_type test(...);
template <class U>
static auto test(U)
-> decltype(static_cast<void(*)(const char*, U&)>(get_val_from_db),
std::true_type{});
static constexpr bool value = decltype(test(std::declval<T>()))::value;
};
template <class T>
inline constexpr bool has_implementation_v = has_implementation<T>::value;
Run Code Online (Sandbox Code Playgroud)
template <class T>
void get_val_from_db(const std::string& key, T& value) {
// very clear error message:
static_assert(has_implementation_v<T>, "Impl. for T missing");
get_val_from_db(key.c_str(), value);
}
Run Code Online (Sandbox Code Playgroud)