Можно ли вызвать ошибку компилятора / компоновщика, если шаблон не был создан с определенным типом? - PullRequest
17 голосов
/ 06 декабря 2011

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

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

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

Обоснование: Я полностью поддерживаю ранние ошибки и, если возможно, хочу обнаружить как можно больше ошибок до времени выполнения, т.е.время компиляции и компоновки.Поскольку проверка наличия загрузчика для этого ресурса будет включать только шаблоны, я надеюсь, что это возможно сделать.


Цель в моих попытках: вызвать ошибку компоновщика при вызове 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 ведут себя здесь корректно, как обсуждалось в связанном вопросе и, в частности, этот ответ .

Ответы [ 3 ]

3 голосов
/ 13 декабря 2011

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

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

2 голосов
/ 13 декабря 2011

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

Вам решать, является ли этот макрос приемлемым или нет.

1 голос
/ 07 декабря 2011
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, то определяют конструктор, который вызывает конструктор родителя, они получат ошибку компилятора с неверным типом. Или, если дочерний класс шаблонизирован, и он создает подкласс и создает экземпляр с неправильным типом, он получит ошибку компилятора.

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 с этими типами.

...