Последующий вопрос к [Отбрасывает ли кастинг указатель на шаблон для создания шаблона?].
Вопрос так же, как гласит название, а остальная часть вопроса является ограничениями и примерами использования шаблона класса, а также мои попытки достичь цели.
Важное ограничение. Пользователь создает экземпляр шаблона путем подкласса моего шаблона класса (а не путем его явного создания, как в моих попытках ниже). Поэтому мне важно, чтобы пользователь, по возможности, не нуждался в дополнительной работе. Просто подклассификация и она должна работать (подкласс фактически регистрируется в словаре уже без того, чтобы пользователь делал что-либо, кроме подкласса дополнительного шаблона класса с 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>
с помощью пути ресурса - Внутри
the_lib::load<my_fancy_struct>
, если ресурс, идентифицированный по пути, уже не кэшируется, я загружаю его с диска - Конкретный
loader
, который будет использоваться в этом случае, создается во время запуска и сохраняется в словаре - Существует словарь для каждого типа ресурса, и они отображают [расширение файла →
loader
pointer] - Если словарь пуст, пользователь либо
- не создавал загрузчик для этого конкретного расширения или
- не создавал загрузчик для этого конкретного ресурса
- Я хочу, чтобы в первом случае мне пришлось исключать исключение из среды выполнения
- Второй случай должен быть обнаружен во время компиляции/ссылки, поскольку он включает в себя шаблоны
Обоснование:. Я сильно склоняюсь к ранним ошибкам, и по возможности я хочу обнаружить как можно больше ошибок до выполнения, т.е. во время компиляции и ссылки. Поскольку проверка того, существует ли загрузчик для этого ресурса, будет включать только шаблоны, я надеюсь, что это возможно.
Цель в моих попытках: Запустить ошибку компоновщика при вызове 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 и GCC 4.5.1
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 уже отлично связывает, как и MSVC10:
void test(){
check_error<int>((foo<int>*)0);
check_error<char>((foo<char>*)0);
}
К сожалению, GCC 4.5.1 и MSVC10 имеют здесь правильное поведение, как обсуждалось в связанном вопросе, и в частности этот ответ.