Безопасно ли ссылаться на тип шаблона C ++, имеющий параметр шаблона, который не совместим с шаблоном? - PullRequest
3 голосов
/ 28 апреля 2019

В следующем примере кода я определяю класс DT (мой тип по умолчанию), который я хочу передать в качестве параметра (ов) для произвольного шаблона.В этом примере я передаю DT в качестве параметров ключа и значения для std :: map.Я на самом деле никогда не пытаюсь создать картуЯ просто хочу использовать картув качестве параметра шаблона для шаблонизируемой функции (в данном примере, функции f ()), которая фактически никогда не ссылается на тип - она ​​используется только для создания экземпляра функции для конкретного типа.(Обратите внимание, что вы не можете создать экземпляр std :: mapпоскольку ключ карты должен быть сопоставим, а DT - нет.)

#include <iostream>
#include <map>

using namespace std;

class DT {};

template <typename T>
string f() {
    return "foo";
}

int main() {
    cout << f<map<DT,DT>>() << endl;
    return 0;
}

Кажется, что это нормально работает с использованием g ++.Я даже пытался передать DT для всех четырех параметров карты (переопределяя компаратор по умолчанию и типы распределителя).Еще работает.Но я боюсь, что эта техника может не сработать с другим шаблоном или с другим компилятором.Поэтому мой вопрос: всегда ли это безопасно для любого шаблона на любом компиляторе c ++, совместимом, скажем, со стандартом c ++ 11 (и более поздними стандартами).Другими словами, всегда ли безопасно передавать полностью несовместимый тип в качестве параметра для шаблона, если вы никогда не пытаетесь создать экземпляр этого шаблона?

Если вам интересно, почему, черт возьми, яЯ хотел бы сделать такую ​​вещь, я пытаюсь настроить класс, где я могу хранить зависящие от типа строки конфигурации.У него будут эти два метода:

template<typename T>
const string& get<T>() const;

template<typename T>
void set<T>(const string& value);

Я в основном работаю к моему удовлетворениюУ него есть несколько приятных особенностей.Например, типы int, const int, int &, const int & и т. Д. Все обрабатываются как один и тот же тип (что я и хочу).И вы можете сохранить строку конфигурации для базового класса, которую впоследствии можно будет извлечь из производного типа, если не найдена запись для более конкретного производного типа.Но для случая, скажем, std :: map, я хотел бы иметь возможность хранить строку конфигурации по умолчанию, используя тип картыкоторый позже будет возвращен как совпадение для любой картыкогда не найдена запись для конкретного типа карты под рукой.Если приведенный выше код действителен, то я думаю, что могу произвести желаемое поведение.

Ответы [ 2 ]

1 голос
/ 28 апреля 2019

К сожалению, я считаю, что стандарт не гарантирует, что std::map<DT, DT> не будет создан. [temp.inst] / 1 указывает только, что

Если специализация шаблона класса не была явно создана или явно специализирована, специализация шаблона класса создается неявно, когда на специализацию ссылаются в контексте, который требует полностью определенного типа объекта или когда полнота типа класса влияет на семантику программы. [& Hellip;]

Обратите внимание, что это говорит нам только о том, что экземпляр шаблона гарантированно создан, но не дает никаких гарантий, что шаблон не будет создан, если такой экземпляр не требуется. [temp.inst] / 10 дает такую ​​гарантию только на

[& hellip;] шаблон функции, шаблон переменной, шаблон члена, не виртуальная функция-член, класс члена, статический член данных шаблона класса или подстановка оператора constexpr if ([stmt .if]), если такой экземпляр не требуется. [& Hellip;]

Обратите внимание на отсутствие шаблонов классов в этом списке. Таким образом, я полагаю, что компилятору теоретически было бы разрешено создавать экземпляры std::map<DT, DT>, даже если он думал, что в этом нет необходимости. Если создание экземпляра шаблона std::map с DT в качестве ключа и типа значения будет недопустимым, у вас возникнет проблема. Я не могу найти никаких гарантий относительно создания экземпляра std::map с типом ключа, который не поддерживает оператор сравнения. Хотя я ожидал, что это будет работать в основном с любой реализацией, я думаю, что теоретически реализация будет иметь возможность, например, иметь static_assert, который проверяет, соответствует ли тип ключа необходимым требованиям. [res.on.functions] / 1 , кажется, применимо (выделено мое):

В некоторых случаях (функции замены, функции-обработчики, операции над типами, используемыми для создания экземпляров стандартных шаблонных компонентов библиотеки) стандартная библиотека C ++ зависит от компонентов, предоставляемых программой C ++. Если эти компоненты не соответствуют их требованиям, Стандарт не устанавливает требований к реализации.

Таким образом, я думаю, что, строго говоря, стандарт не гарантирует, что использование std::map<DT, DT> будет работать & hellip;

Если вы просто хотите использовать std::map<DT, DT> в качестве типа тега для обозначения особого случая, я бы предложил просто не использовать std::map, а что-то другое, например:

template <typename, typename>
struct default_config_tag;

, а затем default_config_tag<DT, DT> или просто DT в качестве вашего тега (не уверен, что аргумент должен быть экземпляром шаблона с двумя типами параметров) должно быть достаточно & hellip;

0 голосов
/ 28 апреля 2019

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

  • Настройка строк конфигурации по умолчанию во время компиляции для определенных типов (например, int) или групп типов (например, std::map<K, V> дляgeneric K и V)

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

Например:

#include <map>
#include <string>
#include <iostream>

template <typename T>
class Config {
 public:
  static const std::string& get() { return Config::getString(); }

  static void set(const std::string& value) { Config::getString() = value; }

  Config(Config const&) = delete;
  void operator=(Config const&) = delete;

 private:
  static std::string& getString() {
    static std::string s(defaultString(dispatch_tag<T>{}));
    return s;
  }

  template <typename U>
  struct dispatch_tag {};

  // Default string unless specified for specific types below.
  template <typename U = T>
  static constexpr std::string_view defaultString(dispatch_tag<U>) {
    return "default";
  }

  // Default config strings for a select number of types.
  static constexpr std::string_view defaultString(dispatch_tag<int>) {
    return "default int";
  }

  template <typename K, typename V>
  static constexpr std::string_view defaultString(
      dispatch_tag<std::map<K, V>>) {
    return "default map";
  }
};

int main() {
  std::cout << Config<int>::get() << "\n";                 // default int
  std::cout << Config<std::string>::get() << "\n";         // default
  std::cout << Config<std::map<int, int>>::get() << "\n";  // default map

  Config<int>::set("custom int");
  Config<std::map<int, int>>::set("custom int-int map");

  std::cout << Config<int>::get() << "\n";                  // custom int
  std::cout << Config<std::map<int, int>>::get() << "\n";   // custom int-int map
  std::cout << Config<std::map<int, char>>::get() << "\n";  // default map
}

Однако это не решает, что вы хотели бы (основываясь на ваших комментариях к собственному сообщению) указать во время выполнения значение резервной строки конфигурации по умолчанию для универсальных типов (скажем, std::map<K, V>).

...