Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specialize assignment operator in template class?

I want to write an aggregate template struct with custom assignment operator, like this:

template <typename T>
struct Foo {
    Foo() = default;
    Foo(const Foo&) = default;

    Foo& operator=(const Foo& f) { ... }

    ...
};

Now, if T is a const-qualified type I want to have:

Foo& operator=(const Foo& f) = delete;

The only way I can think of is to specialize Foo struct:

template<T> struct Foo<const T> {
   Foo& operator=(const Foo& f) = delete;
   ... a lot of code ...
}

But to specialize this struct I must copy-paste all the remaining code (aggregate means no inheritance - at least before C++17 and no possibility to move common code to base class).

Is there any better way to do that?

like image 361
Igor Avatar asked Jun 06 '26 08:06

Igor


2 Answers

I propose a sort of self inheritance: the const specialization that inherit from the generic version

template <typename T>
struct Foo
 {
   Foo() = default;
   Foo(Foo const &) = default;

   Foo& operator= (Foo const & f) { return *this; }
 };

template <typename T>
struct Foo<T const> : public Foo<T>
 {
   Foo& operator= (Foo const & f) = delete;
 };

This way your const specialization inherit all from the generic version, so there is no need of copy and past all the common code, except the operator=() that is deleted.

The following is a full example

template <typename T>
struct Foo
 {
   Foo() = default;
   Foo(Foo const &) = default;

   Foo& operator= (Foo const & f) { return *this; }
 };

template <typename T>
struct Foo<T const> : public Foo<T>
 {
   Foo& operator=(Foo const & f) = delete;
 };

int main () 
 {
   Foo<int>        fi;
   Foo<int const>  fic;

   fi  = fi;   // compile
   // fic = fic; // compilation error
 }
like image 185
max66 Avatar answered Jun 09 '26 01:06

max66


I think you can fully hide the assignment with CRTP. Structurally, it is similar to the self-inheritance technique, but it uses static polymorphism to implement the assignment operator in the base. The const specialized version is deleted, so attempts to invoke the assignment operator will fail.

template <typename D>
struct FooCRTP {
    D & derived () { return *static_cast<D *>(this); }
    D & operator = (const D &rhs) { return derived() = rhs; }
};

template <typename T> struct Foo : FooCRTP<Foo<T>> {};

template <typename T>
struct Foo<const T> : FooCRTP<Foo<const T>>
{
    Foo & operator = (const Foo &) = delete;
};

One advantage of the CRTP version over the self-inheritance technique is that in the CRTP solution, the base class assignment is implemented using the derived's assignment. However, in the self-inheritance technique, the base class assignment is its own implementation, so it may be invoked unexpectedly. For example:

Foo<int> f_int;
Foo<const int> f_cint;

f_cint.Foo<int>::operator=(f_int);

The above code will fail to compile with CRTP, but the compiler will not complain using the self-inheritance technique.

like image 30
jxh Avatar answered Jun 09 '26 01:06

jxh



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!