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

Как избежать "неявного" вызова однопараметрического конструктора в 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 неудобной.

4b9b3361

Ответ 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 )
{}