是否可以安全地引用具有与模板不兼容的模板参数的C++模板类型?
Safe to reference a C++ template type having a template parameter that's not compatible with the template?
在下面的代码示例中,我定义了一个类DT(我的默认类型),我希望能够将其作为任意模板的参数传递。 在此示例中,我将 DT 作为 std::map 的键值参数传递。我实际上从未尝试实例化 map<DT,DT>,我只想使用 map<DT,DT> 作为模板化函数的模板参数(在此示例中为函数 f()),该函数从未实际引用过该类型——它仅用于生成特定于类型的函数实例。 (请注意,您实际上无法实例化 std::map<DT,DT>因为映射的键必须是可比的,但 DT 不是。
#include <iostream>
#include <map>
using namespace std;
class DT {};
template <typename T>
string f() {
return "foo";
}
int main() {
cout << f<map<DT,DT>>() << endl;
return 0;
}
这似乎使用 g++ 工作正常。 我什至尝试为所有四个映射参数传递 DT(覆盖默认比较器和分配器类型)。 仍然有效。但我担心这种技术可能会在其他模板或其他编译器中失败。 所以我的问题是:对于任何符合 c++11 标准(及更高标准)的 c++ 编译器上的任何模板来说,这是否总是安全的。 换句话说,只要您从不尝试实例化该模板,传递完全不兼容的类型作为模板的参数是否始终安全?
如果你想知道我到底为什么要做这样的事情,我正在尝试设置一个类,我可以在其中存储依赖于类型的配置字符串。 它将具有以下两种方法:
template<typename T>
const string& get<T>() const;
template<typename T>
void set<T>(const string& value);
我在很大程度上令我满意。 它有几个不错的功能。 例如,int,const int,int&,const int&等类型都被视为同一类型(这就是我想要的)。 您可以存储基类的配置字符串,如果找不到更具体的派生类型的条目,则以后可由派生类型检索。 但是对于 std::map 的情况,我希望能够使用 map<DT,DT> 类型存储默认配置字符串,稍后将作为任何 map<Key,Value> 的匹配项返回,当找不到手头特定映射类型的条目时。 如果上面的代码有效,那么我认为我可以产生所需的行为。
不幸的是,我相信,该标准并不能保证std::map<DT, DT>
不会被实例化。[temp.inst]/1 仅指定
否则当在需要完全定义的对象类型的上下文中引用专用化时,或者当类类型的完整性影响程序的语义时,将隐式实例化类模板专用化。[...]
请注意,这仅告诉我们何时保证模板被实例化,如果不需要这样的实例化,它并不能保证模板不会被实例化。[temp.inst]/10 仅对
[...]函数模板、变量模板、成员模板、非虚拟成员函数、成员类、类模板的静态数据成员或constexpr if语句([stmt.if])的子语句,除非需要这种实例化。[...]
请注意,此列表中缺少类模板。因此,我相信,编译器理论上被允许实例化,std::map<DT, DT>
甚至认为没有必要这样做。如果实例化std::map
DT
作为键和值类型的模板无效,则会遇到问题。我找不到有关使用不支持比较运算符的密钥类型实例化std::map
的任何保证。虽然我希望这基本上适用于任何实现,但我确实认为理论上允许实现,例如,有一个static_assert
来检查密钥类型是否满足必要的要求。[res.on.functions]/1 似乎适用(强调我的):
在某些情况下(替换函数、处理程序函数、对用于实例化标准库模板组件的类型的操作),C++标准库依赖于C++程序提供的组件。如果这些组件不符合其要求,则标准对实施没有要求。
因此,我认为,严格来说,该标准并不能保证使用std::map<DT, DT>
会起作用......
如果您只是想使用std::map<DT, DT>
作为一种标签类型来指示特殊情况,我建议不要使用std::map
而是使用其他东西,例如:
template <typename, typename>
struct default_config_tag;
然后default_config_tag<DT, DT>
或只是DT
,因为您的标签(不确定您是否需要参数是具有两个类型参数的模板的实例)就足够了......
您已经得到了问题的答案,但是对于本文的读者来说,问题的上下文同样很有趣,因此我认为值得一提的是,您可以将标签调度用于您自己的用例,以:
- 在编译时为特定类型(例如
int
)或类型组(例如 通用K
和V
的std::map<K, V>
)
如果没有标记调度,这可能会很棘手,因为您可能无法部分专用化函数模板。
例如:
#include <map>
#include <string>
#include <iostream>
template <typename T>
class Config {
public:
static const std::string& get() { return Config::getString(); }
static void set(const std::string& value) { Config::getString() = value; }
Config(Config const&) = delete;
void operator=(Config const&) = delete;
private:
static std::string& getString() {
static std::string s(defaultString(dispatch_tag<T>{}));
return s;
}
template <typename U>
struct dispatch_tag {};
// Default string unless specified for specific types below.
template <typename U = T>
static constexpr std::string_view defaultString(dispatch_tag<U>) {
return "default";
}
// Default config strings for a select number of types.
static constexpr std::string_view defaultString(dispatch_tag<int>) {
return "default int";
}
template <typename K, typename V>
static constexpr std::string_view defaultString(
dispatch_tag<std::map<K, V>>) {
return "default map";
}
};
int main() {
std::cout << Config<int>::get() << "n"; // default int
std::cout << Config<std::string>::get() << "n"; // default
std::cout << Config<std::map<int, int>>::get() << "n"; // default map
Config<int>::set("custom int");
Config<std::map<int, int>>::set("custom int-int map");
std::cout << Config<int>::get() << "n"; // custom int
std::cout << Config<std::map<int, int>>::get() << "n"; // custom int-int map
std::cout << Config<std::map<int, char>>::get() << "n"; // default map
}
但是,这并不能解决您希望(根据您对自己帖子的评论)在运行时指定泛型类型(例如std::map<K, V>
)的回退默认配置字符串的值。
- 我收到同义重复编译器错误。我应该如何修复"类型"X"的参数与类型"X"的参数不兼容?
- 字符类型转换不兼容
- Qt:如何使不兼容的发送方/接收方参数兼容?
- 视觉工作室 2017;启用 /permissive 时,类型 "const wchar_t *" 的参数与类型 "PWSTR" 的参数不兼容
- 使用不兼容的分配器复制分配无序列图
- 类型为 "int*" 的参数与 C++ 中错误类型"int**"参数不兼容
- 该对象具有与成员函数不兼容的类型限定符.为什么会出现此错误?
- 我正在尝试将表的地址传递给要在另一个函数中使用的指针,但得到不兼容的指针类型
- 为什么范围算法与 std 的迭代器不兼容?
- Winpcap Findalldevs const char * 与 char * 不兼容
- C++ 类型的参数与 void (__cdecl*)(void) 类型的参数不兼容,当调用 std::atexit()
- 将"std::string {aka std::basic_string}"赋值中的不兼容类型<char>
- 如何在C++中停止调用不兼容的方法?
- OPENCL 警告:不兼容的指针类型将'float __global[16]'传递给类型为 '__global float4 的参数 *
- 函数范围的静态变量如何导致与共享库中函数代码的未来使用不兼容
- 如何修复"方法的类型与 PInvoke 不兼容"
- C++不兼容的迭代器类型
- 编译器错误"在if语句中分配不兼容的类型"
- 程序无法编译:将 'int (*)[3][3]' 赋值为 'int [9][3][3]' 中的不兼容类型
- boost::mpl 不使用(甚至不兼容)std::p air 的原因是什么?