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

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

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

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

17
задан Community 23 May 2017 в 12:34
поделиться