Как избежать "неявного" вызова однопараметрического конструктора в std:: pair
Первоначальная проблема заключалась в том, как работать с std::map<std::wstring, std::wstring> >
безопасным способом, поскольку равные типы ключа и значения чрезвычайно подвержены ошибкам. Поэтому я решил создать простую оболочку для значения:
struct ComponentName
{
std::wstring name;
// I want to prohibit any implicit string-ComponentName conversions!!!
explicit ComponentName(const std::wstring& _name) : name(_name)
{
}
bool operator<(const ComponentName& item_to_compare) const
{
return name < item_to_compare.name;
}
};
typedef std::map<std::wstring, ComponentName> component_names_map;
Но следующий код работает хорошо!
component_names_map component_names;
// Are you sure that ComponentName constructor cannot be called implicitly? ;)
component_names_map::value_type a_pair = std::make_pair(L"Foo", L"Bar");
Это работает, потому что конструктор std::pair<std::wstring, ComponentName>
copy явно использует строковый конструктор ComponentName для назначения экземпляра std::pair<std::wstring, std::wstring>
. Это абсолютно законная операция. Однако он выглядит как "неявный" вызов конструктора ComponentName.
Итак, я знаю причину проблемы, но как я могу избежать этого "неявного" преобразования wstring-ComponentName
?
Самый простой способ - не объявлять конструктор строк, но делает инициализацию ComponentName неудобной.
Ответы
Ответ 1
Я думаю, вы можете законно сделать это, добавив частичную специализацию std::pair
для вашего типа:
namespace std {
template <typename T>
struct pair<T,ComponentName> {
typedef T first_type;
typedef ComponentName second_type;
T first;
ComponentName second;
// The rest of the pair members:
// ....
// Any trick you like to make it fail with assignment/construction from
// pair<std::wstring, std::wstring>
};
}
Обоснование:
В § 17.6.4.2.1 изложены основные правила для специализаций в пространстве имен std
:
"Программа может добавить специализацию шаблона для любой стандартной библиотеки шаблон в пространство имен std, только если декларация зависит от определяемый пользователем тип, а специализация соответствует стандартной библиотеке требования к исходному шаблону и явно не указаны запрещено"
Я не вижу никакого явного запрета, который бы разрешил этот конкретный случай, если вы заполнили остальную часть класса в рамках § 20.3.
Альтернативный, возможно, юридический подход:
Специализируйте std::is_constructible<ComponentName, std::wstring>
таким образом, чтобы value
был ложным. Это указано как требование как оператора присваивания, так и конструктора копирования для std::pair
разных типов. Я также не вижу никаких запретов от быстрого сканирования этого, но я не могу найти ничего, говоря, что для проверки требований требуется выполнение.
Ответ 2
Проблема (в С++ 03) заключается в том, что большинство стандартных реализаций библиотек не соответствуют стандарту. В частности, стандарт утверждает, что когда a std::pair<T,U>
строится из другого std::pair<V,W>
, члены строятся неявными преобразованиями. Проблема в том, что на самом деле очень сложно (если вообще возможно) ограничить это преобразование в реализации шаблонного конструктора pair
, поэтому текущие реализации выполняют явное преобразование аргументов:
template <typename T, typename U>
struct pair {
// ...
template <typename V, typename W>
pair( pair<V,W> const & p ) : first( p.first ), second( p.second ) {}
};
На самом деле я написал сообщение в блоге об этом конкретном случае здесь, и ради этого я попытался предоставить соответствующие конструкторы преобразования здесь, но решение не соответствует стандарту (то есть оно имеет другую подпись, чем та, которая требуется стандартом).
Примечание. В С++ 11 (§20.3.2p12-14) это неявное преобразование также запрещено (из FDIS):
template<class U, class V> pair(pair<U, V>&& p);
Требуется: is_constructible:: значение true и is_constructible:: значение true.
Эффекты: конструктор сначала инициализирует std:: forward (p.first), а второй - std:: forward (p.second).
Примечание. Этот конструктор не должен участвовать в разрешении перегрузки, если U неявно конвертируется в first_type, а V неявно конвертируется в second_type.
Эквивалентные ограничения присутствуют в p9-11 для эквивалента для template<class U, class V> pair(const pair<U, V>& p);
(в случае, если типы не перемещаются)
Ответ 3
Простой:
enum FromString { fromString };
ComponentName( FromString, std::wstring const& aName)
: name( aName )
{}