2

I am currently writing a library, which facilitates deferred calls and registration of callbacks, which must work using gcc, clang and MSVC.

I have encountered something that I find very strange. I have a function with two overloads and I get an error if and only if, the function is defined in the interface. I get the same error using gcc 6.3.0-18 and clang 3.8.1-24.

Create an interface following the recommendation by Google with a protected constructor.

#include <queue>
#include <memory>

template <class T>
class IA {
 public:
  virtual ~IA() = default;
  // virtual bool push(const T& souce) = 0; /* Issue */
  virtual bool push(T&& source) = 0;
 protected:
  IA() = default;
};

A class implementing the interface

template <class T>
class A : public IA<T> {
 public:
  ~A() override {};

  bool push(const T& source) {
    m_queue.push(source);
    return true;
  }

  bool push(T&& source) {
    m_queue.push(std::move(source));
    return true;
  }
 private:
  std::queue<T> m_queue;
};

Now, if I instantiate the class using a std::unique_ptr,

int main() {
  A<std::unique_ptr<float> > a;
  return 0;
}

Everything works fine. Unless, I uncomment the function with prototype bool push(const T& soruce) from the interface. Then, I get the error.

/usr/include/c++/6/ext/new_allocator.h:120:4: error: use of deleted 
function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const 
std::unique_ptr<_Tp, _Dp>&) [with _Tp = float; _Dp = 
std::default_delete<float>]’
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }

I am fully aware of the fact that std::unique_ptr's cannot be copied, but why is the error not appearing if the function is present only in the implementation.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Jens Munk
  • 4,627
  • 1
  • 25
  • 40
  • I think the problem is related with the copy constructor, unique_ptr does allow a copy constructor or assigment. – JTejedor Nov 20 '17 at 22:17
  • I know that. The strange thing is that the error disappear if I remove the function from the interface. Everything still works. The reason why I want to keep the overload is for backwards compatibility – Jens Munk Nov 20 '17 at 22:19
  • Well. After experimenting a bit, I think the problem is when the template function is evaluated by the compiler. In the case when the line is not commented, the template member function is evaluated when the object is created, so the error shows when you declare an object. But when the copy function member is not in code, the member function is evaluated when it is used. – JTejedor Nov 20 '17 at 22:39
  • You may be right, but I still find it a bit strange, why it works if I remove the declaration in the interface. I found I neat way to get around the issue using `std::enable_if` – Jens Munk Nov 20 '17 at 22:46
  • I have used the procedure found here, https://stackoverflow.com/questions/27073082/conditionally-disabling-a-copy-constructor – Jens Munk Nov 20 '17 at 22:48
  • This code does NOT contain any universal references. `T&&` is only a forward reference / universal reference if `T` is a parameter of the function template, but here it's a parameter of the class template. – aschepler Nov 20 '17 at 23:13
  • @aschepler You are right, my bad – Jens Munk Nov 20 '17 at 23:22

1 Answers1

3

The compiler only complains about this when you instantiate a template with a non-copyable type like std::unique_ptr<T>. The way template resolution works in C++ is through a process called monomorphization. The compiler creates distinct types for each template instance. A<int> and A<float> are unrelated after monomorphization is completed. On the other hand, this means that the compiler only creates these instances for the types you actually use.

In your particular case, it will just compile fine as long as you don't use a noncopyable type for T, or if you use one and uncomment the code that calls its copy constructor - bool push(const T& source).

In addition to that, it seems like compilers don't even compile the first push method when they are able to infer that it is unused (see live example). This means that it can contain parseable, but incorrect C++ code. If the method is virtual, compilers can no longer infer that it's not used (as they could be called via a vtable), so it has to generate code for the method and thus encounters the error.

jupp0r
  • 4,502
  • 1
  • 27
  • 34
  • 1
    The source compiles even when I use a non-copyable type, e.g. `std::unique_ptr` and keep the function `bool push(const T& source)` in the implementation. That's what I find strange. I have made a work-around using `std::enable_if` and `is_copy_constructible`. – Jens Munk Nov 20 '17 at 23:08
  • 1
    Hmm. That is a surprise to me.... I solved my issue by adding another template argument `bool = std::is_copy_constructible` for both interface and implementations. Thanks again. It demonstrates why even for templates, it makes sense to define an interface... – Jens Munk Nov 21 '17 at 00:13
  • It also demonstrates the performance costs of doing so - redundant generated code at compile time and the vtable lookup at runtime. Thanks for the question anyway, I learned something! – jupp0r Nov 21 '17 at 00:37