3

I'm constructing a simple container class but run into some problems (reassembling the ones in Visual C++ 2010, rvalue reference bug?)

#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;
}

The above code gives the following error:

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.

How can one remedy this error without using tricks like the ones described in the linked question?

Thanks!

Edit: What confuses me the most is why isn't any of the two specialized constructors called. They fit the call a lot better.

  // 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)) {
  }
Community
  • 1
  • 1
Bartłomiej Siwek
  • 1,447
  • 2
  • 17
  • 26

2 Answers2

2

Your constructor:

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

is overly generic and the best way to solve your problem is to restrict the type which can be deduced as S0. This is basically what the linked answer does to. But perhaps I can make it prettier for you.

Here it is, in 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)) {
  }

If that is just too ugly, you might consider:

#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)) {
}

Note that this will not only solve your problem, but if your clients subsequently ask questions like:

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

they will now get the correct answer. As you currently have it coded, the above trait always answers true.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Added a edit to the question - why aren't the specialized constructors called? – Bartłomiej Siwek Aug 09 '11 at 01:33
  • Just a note that this nice solution uses new features that are not implemented in VC2010. – Bo Persson Aug 09 '11 at 04:20
  • @Bo: I think it'd be possible to forego the use of variadic macros easily (add another pair of parentheses) and `enable_if` can be written down easily. I think that `is_convertible` can be more or less accurately emulated... What exactly does VC2010 lack ? – Matthieu M. Aug 09 '11 at 06:23
  • @Mathieu - It doesn't do default function template parameters, so the `class = enable_if...` fails to compile. – Bo Persson Aug 09 '11 at 06:26
2

Your linked question already answers this. Let's define

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

When you say MD t2(t1);, you would like to call the constructor MF::MF(const MD &). However, the constructor template <typename T> MF::MF(T&&) matches better, because it takes T = MD& and thus resolves to MF::MF(MD&), which is a better match due to the absence of const.

To resolve this, you should essentially get rid of the MF(T&&) constructor, as Howard suggested already. Since you only intend that constructor for values anyway, my first suggestion would be to change the signature to MF(const T &), which would already solve your problem. Another solution would be to add a constructor with signature MF(MD&) (non-const). That's ugly, though. Finally, you could call the constructor explicity at the call site: MD t2(MF(t1)), or MD t2(std::forward<MF>(t1)) or even MD t2(std::move(t1)), if that's an option.

Finally note that if you are only dealing with primitive members, there's nothing to be gained from explicit moves, so you might as well not bother defining all those constructors separately.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Added a edit to the question - why aren't the specialized constructors called? – Bartłomiej Siwek Aug 09 '11 at 01:33
  • Just as I said. First off, `MD(MF&&)` won't even figure because you're not passing an rvalue reference. Second, `MF(const MD &)` loses out against `MD(T&&)`, which matches for `T = MF&` and becomes `MD(MF&)`. (Note that it is `T` itself that is of refernce type, which cannot happen in `MyType`!) – Kerrek SB Aug 09 '11 at 01:45
  • Thanks! Now I get what's wrong. Also it looks like the creators of a tuple type for GCC and associated family had the same problem (http://gcc.gnu.org/ml/libstdc++/2008-02/msg00047.html) which they fixed with a non-const reference constructor. Gonna follow in their steps. – Bartłomiej Siwek Aug 09 '11 at 02:40