RValue, разрешение шаблона и конструкторы копирования (в Visual C++ 2010)

Я создаю простой контейнерный класс, но сталкиваюсь с некоторыми проблемами (повторная сборка в Visual C++ 2010, ошибка ссылки rvalue?)

#include <cassert>
#include <utility>

template<typename T0>
class MyType {
 public:
  typedef T0 value_type;

  // Default constructor
  MyType() : m_value() {
  }

  // Element constructor
  explicit MyType(const T0 &c_0) : m_value(c_0) {
  }

  template<typename S0>
  explicit MyType(S0 &&c_0) : m_value(std::forward<S0>(c_0)) {
  }

  // Copy constructor
  MyType(const MyType &other) : m_value(other.m_value) {
  }

  MyType(MyType &&other) : m_value(std::forward<value_type>(other.m_value)) {
  }

  // Copy constructor (with convertion)
  template<typename S0>
  MyType(const MyType<S0> &other) : m_value(other.m_value) {
  }

  template<typename S0>
  MyType(MyType<S0> &&other) : m_value(std::move(other.m_value)) {
  }

  // Assignment operators
  MyType &operator=(const MyType &other) {
    m_value = other.m_value;
    return *this;
  }

  MyType &operator=(MyType &&other) {
    m_value = std::move(other.m_value);
    return *this;
  }

  template<typename S0>
  MyType &operator=(const MyType<S0> &other) {
    m_value = other.m_value;
    return *this;
  }

  template<typename S0>
  MyType &operator=(MyType<S0> &&other) {
    m_value = std::move(other.m_value);
    return *this;
  }

  // Value functions
  value_type &value() {
    return m_value;
  }

  const value_type &value() const {
    return m_value;
  }

 private:
  template<typename S0>
  friend class MyType;

  value_type m_value;
};

int main(int argc, char **argv) {
  MyType<float>  t1(5.5f);
  MyType<double> t2(t1);

    return 0;
}

Приведенный выше код дает следующую ошибку:

1>ClCompile:
1>  BehaviorIsolation.cpp
1>behaviorisolation.cpp(18): error C2440: 'initializing' : cannot convert from 'MyType<T0>' to 'double'
1>          with
1>          [
1>              T0=float
1>          ]
1>          No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
1>          behaviorisolation.cpp(78) : see reference to function template instantiation 'MyType<T0>::MyType<MyType<float>&>(S0)' being compiled
1>          with
1>          [
1>              T0=double,
1>              S0=MyType<float> &
1>          ]
1>behaviorisolation.cpp(18): error C2439: 'MyType<T0>::m_value' : member could not be initialized
1>          with
1>          [
1>              T0=double
1>          ]
1>          behaviorisolation.cpp(73) : see declaration of 'MyType<T0>::m_value'
1>          with
1>          [
1>              T0=double
1>          ]
1>
1>Build FAILED.

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

Спасибо!

Редактировать: Меня больше всего смущает, почему не вызывается ни один из двух специализированных конструкторов. Они гораздо лучше подходят для вызова.

  // Copy constructor (with convertion)
  template<typename S0>
  MyType(const MyType<S0> &other) : m_value(other.m_value) {
  }

  template<typename S0>
  MyType(MyType<S0> &&other) : m_value(std::move(other.m_value)) {
  }

person Bartłomiej Siwek    schedule 09.08.2011    source источник


Ответы (2)


Ваш связанный вопрос уже отвечает на это. Давайте определим

typedef MyType<float>  MF;
typedef MyType<double> MD;

Когда вы говорите MD t2(t1);, вы хочете вызвать конструктор MF::MF(const MD &). Однако конструктор template <typename T> MF::MF(T&&) соответствует лучше, потому что он принимает T = MD& и, таким образом, разрешается в MF::MF(MD&), что является лучшим соответствием из-за отсутствия const.

Чтобы решить эту проблему, вы должны избавиться от конструктора MF(T&&), как уже предлагал Говард. Поскольку вы в любом случае предназначаете этот конструктор только для значений, моим первым предложением было бы изменить подпись на MF(const T &), что уже решило бы вашу проблему. Другим решением было бы добавить конструктор с сигнатурой MF(MD&) (неконстантный). Это некрасиво, однако. Наконец, вы можете явно вызвать конструктор в месте вызова: MD t2(MF(t1)), или MD t2(std::forward<MF>(t1)), или даже MD t2(std::move(t1)), если это возможно.

Наконец, обратите внимание, что если вы имеете дело только с примитивными элементами, явные перемещения ничего не дадут, так что вам лучше не заморачиваться с определением всех этих конструкторов по отдельности.

person Kerrek SB    schedule 09.08.2011
comment
Добавил правку в вопрос - почему не вызываются специализированные конструкторы? - person Bartłomiej Siwek; 09.08.2011
comment
Как я уже сказал. Во-первых, MD(MF&&) даже не будет фигурировать, потому что вы не передаете ссылку rvalue. Во-вторых, MF(const MD &) проигрывает MD(T&&), который совпадает с T = MF& и становится MD(MF&). (Обратите внимание, что сам T имеет ссылочный тип, чего не может быть в MyType<T>!) - person Kerrek SB; 09.08.2011
comment
Спасибо! Теперь я понимаю, что не так. Также похоже, что создатели типа кортежа для GCC и связанного с ним семейства столкнулись с той же проблемой (gcc.gnu.org/ml/libstdc++/2008-02/msg00047.html), который они исправили с помощью неконстантного конструктора ссылок. Собираюсь следовать по их стопам. - person Bartłomiej Siwek; 09.08.2011

Ваш конструктор:

 template<typename S0>
  explicit MyType(S0 &&c_0)

является слишком общим, и лучший способ решить вашу проблему — ограничить тип, который можно вывести как S0. Это в основном то, что делает связанный ответ. Но, возможно, я смогу сделать его красивее для вас.

Вот он, в std::C++11:

  template<typename S0,
           class = typename std::enable_if
           <
               std::is_convertible<S0, T0>::value
           >::type>
  explicit MyType(S0 &&c_0) : m_value(std::forward<S0>(c_0)) {
  }

Если это слишком уродливо, вы можете подумать:

#define restrict_to(x...) class = typename std::enable_if<x>::type

...

template<typename S0, restrict_to(std::is_convertible<S0, T0>::value)>
explicit MyType(S0 &&c_0) : m_value(std::forward<S0>(c_0)) {
}

Обратите внимание, что это решит не только вашу проблему, но и если впоследствии ваши клиенты будут задавать такие вопросы, как:

std::is_convertible<X, MyType<T>>::type

теперь они получат правильный ответ. Поскольку вы в настоящее время закодировали его, указанный выше признак всегда отвечает true.

person Howard Hinnant    schedule 09.08.2011
comment
Добавил правку в вопрос - почему не вызываются специализированные конструкторы? - person Bartłomiej Siwek; 09.08.2011
comment
Просто отметим, что это хорошее решение использует новые функции, которые не реализованы в VC2010. - person Bo Persson; 09.08.2011
comment
@Bo: Я думаю, что можно было бы легко отказаться от использования вариативных макросов (добавьте еще пару скобок), а enable_if можно легко записать. Я думаю, что is_convertible можно более-менее точно эмулировать... Чего именно не хватает в VC2010? - person Matthieu M.; 09.08.2011
comment
@Mathieu - он не использует параметры шаблона функции по умолчанию, поэтому class = enable_if... не компилируется. - person Bo Persson; 09.08.2011