1

I have simple class:

#include <utility>

template<class T, class... Ts>
T make(const Ts&... args) //generic class maker
{
    return T(args...);
}

template<class T>
class A
{
public:
    A(void)           : x_(T()), y_(T()) {}
    explicit A(int x) : x_(x), y_(x) {}
    A(int x, int y)   : x_(x), y_(y) {}
    A(const A& other) : x_(other.x_), y_(other.y_) {}
    A(A&& temp)       : x_(std::move(temp.x_)), y_(std::move(temp.y_)) {}

    auto& operator =(A other);
    auto& operator +=(const A& other);
    auto  operator + (const A& other) const;
private:
    T x_;
    T y_;
};

template<class T>
auto& A<T>::operator =(A<T> other)
{
    std::swap(*this, other); return *this;
}

template<class T>
auto& A<T>::operator+=(const A<T>& other)
{
    x_ += other.x_;
    y_ += other.y_;
    return *this;
}

template<class T>
auto A<T>::operator+(const A<T>& other) const
{
    return make<A<T>>(*this) += other;
}

int main()
{
    A<int> first(1);
    auto second = A<int>(2,2);
    auto third  = make<A<int>>(second+first);
    auto fourth = make<A<int>>(4);
    auto fifth  = make<A<int>>(5,5);
}

But when I try to deduce operator+ type from *this, I get strange errors I don't expect:

template<class T>
auto A<T>::operator+(const A<T>& other) const
{
    return make<typename std::remove_cv<decltype(*this)>::type>(*this) += other;
}

int main()
{
    auto solo = A<int>();
    solo + solo;
}

Errors:

error: passing 'const A<int>' as 'this' argument of 'auto& A<T>::operator+=(const A<T>&) 
[with T = int]' discards qualifiers [-fpermissive]
     return make<typename std::remove_cv<decltype(*this)>::type>(*this) += other;
                                                                        ^
error: use of 'auto& A<T>::operator+=(const A<T>&) [with T = int]' before deduction 
of 'auto'
error: invalid use of 'auto'
     return make<typename std::remove_cv<decltype(*this)>::type>(*this) += other;
                                                                           ^
error: invalid use of 'auto'

If I'm right, make(...) should create new object (which should be free of cv), so why do I get discards qualifiers [-fpermissive] error?

xinaiz
  • 7,744
  • 6
  • 34
  • 78

2 Answers2

3

The problem is you're passing a const object as the left-hand argument of operator+=, which is a non-const member. Why is that happening?

Let's parse:

typename std::remove_cv<decltype(*this)>::type

this for a const member function will be A<T> const* const. Dereferencing that gives us A<T> const& (not A<T> const!). But references don't have cv-qualifications, so remove_cv doesn't actually do anything. As a result, the type of the above is:

A<T> const&

which isn't what you wanted at all. You wanted to create a copy of this, not bind a reference to const to this. What we need to do instead is drop the reference. There's a type trait for that:

typename std::decay<decltype(*this)>::type

So what you want to do is:

template<class T>
auto A<T>::operator+(const A<T>& other) const
{
    return make<std::decay_t<decltype(*this)>>(*this) += other;
}

But that's horribly complicated. I'd prefer something like:

template <class T>
class A {
    ...
    friend A<T> operator+(A lhs, A const& rhs) {
        lhs += rhs;
        return lhs;
    }
};
Barry
  • 286,269
  • 29
  • 621
  • 977
  • `return lhs += rhs;` given a typical `+=` will cause a copy, which is pretty terrible. – T.C. Mar 28 '16 at 19:12
  • @T.C. Meant to return separately. But we're making a copy regardless. If this isn't a good approach, then do you want to fix [this answer](http://stackoverflow.com/a/4421719/2069064)? – Barry Mar 28 '16 at 19:16
  • Separately is OK (the return implicitly moves from `lhs`). In one line isn't (because when `lhs += rhs` is an lvalue, as it usually is, you copy `lhs` *again*). – T.C. Mar 28 '16 at 19:19
2

*this is an expression, so when queried with decltype it yields an lvalue reference type, and so, that std::remove_cv trait is applied to a reference type, while it should be applied to the type referred to:

return make<typename std::remove_cv<
              typename std::remove_reference<decltype(*this)>::type
           >::type>(*this) += other;
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160