后续问题[转换为指向模板的指针是否会实例化该模板?] https://stackoverflow.com/questions/8379002/does-a-casting-to-a-pointer-to-a-template-instantiate-that-template.
问题正如标题所说,剩下的问题是类模板的约束和使用示例,以及我为实现目标所做的尝试。
一个重要的约束:用户通过子类化我的类模板来实例化模板(而不是像我下面的尝试那样显式实例化它)。因此,对我来说重要的是,如果可能的话,用户不需要做任何额外的工作。只需子类化就应该可以工作(子类实际上已经在字典中注册了自己,除了使用 CRTP 对附加类模板进行子类化之外,用户无需执行任何操作,并且子类永远不会被创建它的用户直接使用)。如果确实没有其他方法,我愿意接受用户需要做额外工作的答案(例如从额外的基础派生)。
解释如何使用类模板的代码片段:
// the class template in question
template<class Resource>
struct loader
{
typedef Resource res_type;
virtual res_type load(std::string const& path) const = 0;
virtual void unload(res_type const& res) const = 0;
};
template<class Resource, class Derived>
struct implement_loader
: loader<Resource>
, auto_register_in_dict<Derived>
{
};
template<class Resource>
Resource load(std::string const& path){
// error should be triggered here
check_loader_instantiated_with<Resource>();
// search through resource cache
// ...
// if not yet loaded, load from disk
// loader_dict is a mapping from strings (the file extension) to loader pointers
auto loader_dict = get_all_loaders_for<Resource>();
auto loader_it = loader_dict.find(get_extension(path))
if(loader_it != loader_dict.end())
return (*loader_it)->load(path);
// if not found, throw some exception saying that
// no loader for that specific file extension was found
}
// the above code comes from my library, the code below is from the user
struct some_loader
: the_lib::implement_loader<my_fancy_struct, some_loader>
{
// to be called during registration of the loader
static std::string extension(){ return "mfs"; }
// override the functions and load the resource
};
现在以表格形式:
- 用户来电
the_lib::load<my_fancy_struct>
带有资源路径
- Inside
the_lib::load<my_fancy_struct>
,如果路径标识的资源尚未缓存,我从磁盘加载它
- 具体的
loader
在这种情况下使用的是在启动时创建并保存在字典中
- 每个资源类型都有一个字典,它们映射[文件扩展名 ->
loader
指针]
- If the dictionary is empty, the user either
- 没有为该特定扩展创建加载程序或
- 没有为该特定资源创建加载程序
- 我只希望第一种情况让我抛出运行时异常
- 第二种情况应该在编译/链接时检测,因为它涉及模板
理由:我非常赞成早期错误,如果可能的话,我希望在运行时(即编译和链接时)之前检测到尽可能多的错误。由于检查该资源的加载器是否存在只涉及模板,因此我希望可以做到这一点。
我尝试的目标:在调用时触发链接器错误check_error<char>
.
// invoke with -std=c++0x on Clang and GCC, MSVC10+ already does this implicitly
#include <type_traits>
// the second parameter is for overload resolution in the first test
// literal '0' converts to as well to 'void*' as to 'foo<T>*'
// but it converts better to 'int' than to 'long'
template<class T>
void check_error(void*, long = 0);
template<class T>
struct foo{
template<class U>
friend typename std::enable_if<
std::is_same<T,U>::value
>::type check_error(foo<T>*, int = 0){}
};
template struct foo<int>;
void test();
int main(){ test(); }
鉴于上面的代码,以下test
定义确实实现了 MSVC、GCC 4.4.5 和海湾合作委员会4.5.1 http://ideone.com/7FRrU:
void test(){
check_error<int>(0, 0); // no linker error
check_error<char>(0, 0); // linker error for this call
}
但是,它不应该这样做,因为传递空指针不会触发 ADL。为什么需要 ADL?因为标准是这么说的:
§7.3.1.2 [namespace.memdef] p3
[...] 如果一个friend
非局部类中的声明首先声明一个类或函数,友元类或函数是最内层封闭命名空间的成员。在该命名空间范围内提供匹配的声明之前,通过非限定查找或限定查找都找不到好友的名称(在授予友谊的类定义之前或之后)。 [...]
通过强制转换触发 ADL,如以下定义所示test
,在 Clang 3.1 和 GCC 4.4.5 上实现了目标,但是GCC 4.5.1 已经链接良好 http://ideone.com/0kAMz,与 MSVC10 一样:
void test(){
check_error<int>((foo<int>*)0);
check_error<char>((foo<char>*)0);
}
遗憾的是,GCC 4.5.1 和 MSVC10 在这里具有正确的行为,正如链接问题中所讨论的,特别是这个答案 https://stackoverflow.com/a/8383216/500104.