Подтвердить что ты не робот

Возможно ли инициировать ошибку компилятора/компоновщика, если шаблон не был создан с определенным типом?

Последующий вопрос к [Отбрасывает ли кастинг указатель на шаблон для создания шаблона?].

Вопрос так же, как гласит название, а остальная часть вопроса является ограничениями и примерами использования шаблона класса, а также мои попытки достичь цели.

Важное ограничение. Пользователь создает экземпляр шаблона путем подкласса моего шаблона класса (а не путем его явного создания, как в моих попытках ниже). Поэтому мне важно, чтобы пользователь, по возможности, не нуждался в дополнительной работе. Просто подклассификация и она должна работать (подкласс фактически регистрируется в словаре уже без того, чтобы пользователь делал что-либо, кроме подкласса дополнительного шаблона класса с 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 имеют здесь правильное поведение, как обсуждалось в связанном вопросе, и в частности этот ответ.

4b9b3361

Ответ 1

Подумав немного о вашей проблеме, я не вижу никакого способа добиться этого. Вам нужен способ заставить экземпляр "экспортировать" что-то вне шаблона, чтобы его можно было получить без ссылки на экземпляр. A friend функция с ADL была хорошей идеей, но, к сожалению, было показано, что для работы ADL, шаблон должен быть создан. Я попытался найти другой способ "экспортировать" что-то из шаблона, но не смог найти его.

Обычное решение вашей проблемы состоит в том, чтобы пользователь специализировался на классе признаков:

template < typename Resource >
struct has_loader : boost::mpl::false_ {};

template <>
struct has_loader< my_fancy_struct > : boost::mpl::true_ {};

Чтобы скрыть это от пользователя, вы можете предоставить макрос:

#define LOADER( loaderName, resource ) \
template <> struct has_loader< resource > : boost::mpl::true_ {}; \
class loaderName \
  : the_lib::loader< resource > \
  , the_lib::auto_register_in_dict< loaderName >

LOADER( some_loader, my_fancy_struct )
{
  public:
    my_fancy_struct load( std::string const & path );
};

Вам решать, подходит ли этот макрос или нет.

Ответ 2

Компилятор запускает функцию шаблона всякий раз, когда на него ссылаются, и доступна полная спецификация шаблона. Если ни один из них не доступен, компилятор не делает этого и надеется, что какая-то другая единица перевода будет его экземпляр. То же самое верно, например, для конструктора по умолчанию вашего базового класса.

Файл header.h:

template<class T>
class Base
{
public:
   Base();
};

#ifndef OMIT_CONSTR
template<class T>
Base<T>::Base() { }
#endif

Файл client.cc:

#include "header.h"

class MyClass : public Base<int>
{
};


int main()
{
   MyClass a;
   Base<double> b;
}

Файл check.cc:

#define OMIT_CONSTR
#include "header.h"

void checks()
{
   Base<int> a;
   Base<float> b;
}

Тогда:

 $ g++ client.cc check.cc
/tmp/cc4X95rY.o: In function `checks()':
check.cc:(.text+0x1c): undefined reference to `Base<float>::Base()'
collect2: ld returned 1 exit status

EDIT: (попытка применить это к конкретному примеру)

Я буду называть этот файл "loader.h":

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;

  loader();
};

template<class Resource>
class check_loader_instantiated_with : public loader<Resource> {
  virtual Resource load(std::string const& path) const { throw 42; }
  virtual void unload(Resource const& res) const { }
};

template<class Resource>
Resource load(std::string const& path){
  // error should be triggered here
  check_loader_instantiated_with<Resource> checker;
  // ...
}

И еще один файл "loader_impl.h":

#include "loader.h"
template<class Resource>
loader<Resource>::loader() { }

Это решение имеет одну слабую точку, о которой я знаю. Каждый блок компиляции имеет выбор, включающий только loader.h или loader_impl.h. Вы можете определять загрузчики только в единицах компиляции, которые включают loader_impl, и в этих единицах компиляции проверка ошибок отключена для всех загрузчиков.

Ответ 3

template <class T>
class Wrapper {};

void CheckError(Wrapper<int> w);

template <class T>
class GenericCheckError
{
public:
    GenericCheckError()
    {
        Wrapper<T> w;
        CheckError(w); 
    }
};

int main()
{
    GenericCheckError<int> g1; // this compiles fine
    GenericCheckError<char> g2; // this causes a compiler error because Wrapper<char> != Wrapper<int>
    return 0;
}

Edit:

Хорошо, это как можно ближе. Если они подклассы и либо создают экземпляр OR, либо определяют конструктор, который вызывает родительский конструктор, они получат ошибку компилятора с неправильным типом. Или, если шаблон child tempatized, и они подклассы и создают экземпляр с неправильным типом, они получат ошибку компилятора.

template <class T> class Wrapper {};
void CheckError(Wrapper<int> w) {}

template <class T>
class LimitedTemplateClass
{
public:
    LimitedTemplateClass()
    {
        Wrapper<T> w;
        CheckError(w);
    }
};

// this causes no compiler error
class UserClass : LimitedTemplateClass<int>
{
    UserClass() : LimitedTemplateClass<int>() {}
};

// this alone (no instantiation) causes a compiler error
class UserClass2 : LimitedTemplateClass<char>
{
    UserClass2() : LimitedTemplateClass<char>() {}
};

// this causes no compiler error (until instantiation with wrong type)
template <class T>
class UserClass3 : LimitedTemplateClass<T>
{
};

int main()
{
    UserClass u1; // this is fine
    UserClass2 u2; // this obviously won't work because this one errors after subclass declaration
    UserClass3<int> u3; // this is fine as it has the right type
    UserClass3<char> u4; // this one throws a compiler error
    return 0;
}

Очевидно, вы можете добавлять другие принятые типы, определяя дополнительные функции CheckError с этими типами.