11

I have a template like this:

template<typename T>
struct foo {
  T m_t;
  foo(T t) : m_t(t) {}
};

The problem is that I want to support both small/regular types and huge types (like matrices) for T. Do you recommend I write the constructor initializer list like this

foo (T t) : m_t(std::move(t)) {}

and require that the type T always support move construction, even for smaller types? Are there better ways?

7cows
  • 4,974
  • 6
  • 25
  • 30

3 Answers3

12

and require that the type T always support move construction, even for smaller types?

Any type that is copy constructable is also move constructable. Moving in those cases simply calls the copy constructor. Thus, there's no reason not to use m_t(std::move(t)).

An alternative is to use references instead:

foo (T const& t) : m_t(t) {}
foo (T&& t) : m_t(std::move(t)) {}

This has the benefit of only involving one construction rather than two.

Pubby
  • 51,882
  • 13
  • 139
  • 180
  • Is your reference way is better than mine? If T is not move constructable it is (or should be) implied that T is small/normal (not big), isn't it? In this case do you think a reference gives me any measurable performance benefits? – 7cows Jun 11 '13 at 13:24
  • @7cows it can give a measurable performance benefit when T has an expensive copy constructor and no move constructor, which is common with C++03 libraries. I usually usually prefer your way though. – Pubby Jun 11 '13 at 18:01
8

Yes, using the move has no disadvantage in that situation. All copyable objects are automatically moveable, so it doesn't matter. In fact, some recommend to always move variables when possible, even integers.

As an alternative, you may consider to use perfect forwarding, as described in this answer:

template <typename T2>
foo(T2&& t) : m_t(std::forward<T2>(t)) {}

If you know that T defines a fast move constructor, it should not matter. Otherwise, providing a constructor foo(const T&) is recommended to avoid unnecessary copies.

Perfect forwarding is only one technique to achieve that. The solution of Pubby to write out the constructor foo(const T&) and foo(T&&) is, of course, also fine. The results are the same, it is mostly a matter of style.

You also asked about small integer types. In theory, passing them by reference is slower than copying them, but the compiler should be able to optimize it to a copy, anyway. I don't think it will make a difference.

So, better optimize for the worst case where T can be huge and does not provide a fast move constructor. Passing by reference is best for that situation and should not be a bad choice in general, either.

Community
  • 1
  • 1
Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239
  • 2
    The problem with perfect forwarding is that it will accept *everything* that `T`'s constructor accepts. – Pubby Jun 10 '13 at 23:43
  • @Pubby Good point. That may be positive or negative, depending on the context. If more control over conversions is needed, both `foo` constructors could additionally be declared as `explicit`. – Philipp Claßen Jun 11 '13 at 00:04
  • @Pubby, that could could be controlled with `enable_if` and an `is_same` check. Granted, by the time you get that written, you're probably looking at the same number of lines as explicitly implementing both constructors... – Nathan Ernst Jun 11 '13 at 00:41
0

Passing by value has the advantage of only having a single constructor (without template), but comes at the price of one additional move construction compared to the aforementioned alternatives.

However, there is another yet unmentioned drawback of both pass-by-value and the non-template solution given by Pubby: Moving won't work if the copy constructor is defined as T(T&); (note the reference to non-const). Rvalue-references can bind to lvalue-references-to-const, but not to lvalue-references-to-non-const, so the compiler can't call the copy constructor.

To resolve this issue, you can simply add a third overload foo (T & t) : m_t(t) {} to Pubby's solution.

acs
  • 84
  • 4