Последующий вопрос к [Создает ли приведение к указателю на шаблон экземпляр этого шаблона?] .
Вопрос такой же, как и в названии, а остальная часть вопроса связана с ограничениями и примерами использования шаблона класса, а также моими попытками достичь цели.
Важное ограничение: пользователь создает экземпляр шаблона путем создания подкласса моего шаблона класса (а не путем его явного создания, как в моих попытках ниже). Поэтому для меня важно, чтобы, по возможности, пользователю не нужно было выполнять дополнительную работу.Просто подклассы, и это должно работать (подкласс фактически регистрируется в словаре уже без каких-либо действий пользователя, кроме подкласса дополнительного шаблона класса с CRTP, и подкласс никогда не используется напрямую пользователем, который его создал). Однако я готов принять ответы, в которых пользователю необходимо проделать дополнительную работу (например, получить от дополнительной базы), если на самом деле нет другого пути.
Фрагмент кода, объясняющий, как будет использоваться шаблон класса:
// the class template in question
template
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
struct implement_loader
: loader
, auto_register_in_dict
{
};
template
Resource load(std::string const& path){
// error should be triggered here
check_loader_instantiated_with();
// 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();
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
{
// to be called during registration of the loader
static std::string extension(){ return "mfs"; }
// override the functions and load the resource
};
А теперь в табличной форме:
the_lib :: load
с путем к ресурсу the_lib :: load
, если ресурс, указанный в пути, еще не кэширован, я загружаю его с диска
в этом случае создается во время запуска и сохраняется в словаре загрузчик
указатель] Обоснование: Я решительно выступаю за ранние ошибки и, если возможно, я хочу обнаружить как можно больше ошибок до выполнения, то есть во время компиляции и компоновки . Поскольку проверка того, существует ли загрузчик для этого ресурса, будет включать только шаблоны, я надеюсь, что это возможно.
Цель моих попыток: вызвать ошибку компоновщика при вызове check_error
.
// invoke with -std=c++0x on Clang and GCC, MSVC10+ already does this implicitly
#include
// the second parameter is for overload resolution in the first test
// literal '0' converts to as well to 'void*' as to 'foo*'
// but it converts better to 'int' than to 'long'
template
void check_error(void*, long = 0);
template
struct foo{
template
friend typename std::enable_if<
std::is_same::value
>::type check_error(foo*, int = 0){}
};
template struct foo;
void test();
int main(){ test(); }
Учитывая приведенный выше код, следующее определение теста
действительно достигает цели для MSVC, GCC 4.4.5 и GCC 4.5.1 :
void test(){
check_error(0, 0); // no linker error
check_error(0, 0); // linker error for this call
}
Однако этого не должно быть. это, поскольку передача нулевого указателя не запускает ADL. Зачем нужен ADL? Потому что в стандарте так сказано:
§7.3.1.2 [namespace.memdef] p3
[...] Если объявление
friend
в нелокальном классе сначала объявляет класс или функцию, дружественный класс или функция является членом самого внутреннего включающего пространства имен. Имя друга не может быть найдено с помощью неквалифицированного поиска или квалифицированного поиска до тех пор, пока не будет предоставлено объявление соответствия в этой области пространства имен (либо до, либо после определения класса, предоставляющего дружбу). [...]
Запуск ADL через приведение типов, как в следующем определении теста
, достигает цели в Clang 3.1 и GCC 4.4.5, но GCC 4.5.1 уже связывает отлично , как и MSVC10:
void test(){
check_error((foo*)0);
check_error((foo*)0);
}
К сожалению, GCC 4.5.1 и MSVC10 имеют здесь правильное поведение, как обсуждается в связанном вопросе и, в частности, в этом ответе .