1

I'm trying to create a template wrapper class which inherits from its template parameter and which overrides all of the overloads of a particular base member function in one go. Here's an example:

#include <cassert>
#include <string>   
#include <utility>

template <class T>
class Wrapper: public T {
public:
  template <typename... Args>
  Wrapper<T>& operator=(Args&&... args) {
    return this_member_fn(&T::operator=, std::forward<Args>(args)...);
  }

private:
  template <typename... Args>
  Wrapper<T>& this_member_fn(T& (T::*func)(Args...), Args&&... args) {
    (this->*func)(std::forward<Args>(args)...);
    return *this;
  }   
};

int main(int, char**) {
  Wrapper<std::string> w;
  const std::string s("!!!");
  w = s;
  assert(w == s);
  w = std::string("???");
  assert(w == std::string("???"));
  return 0;
}

The idea is that the template for Wrapper<T>::operator= will select the correct T::operator= at compile-time based on its argument, and then forward those arguments on. If I build with

gcc -std=c++11 -W -Wall -Wextra -pedantic test.cpp -lstdc++

I get the following complaints from gcc:

test.cpp: In instantiation of ‘Wrapper<T>& Wrapper<T>::operator=(Args&& ...) [with Args = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >}; T = std::basic_string<char>]’:
test.cpp:26:24:   required from here
test.cpp:10:69: error: no matching function for call to ‘Wrapper<std::basic_string<char> >::this_member_fn(<unresolved overloaded function type>, std::basic_string<char>)’
test.cpp:10:69: note: candidate is:
test.cpp:15:15: note: Wrapper<T>& Wrapper<T>::this_member_fn(T& (T::*)(Args ...), Args&& ...) [with Args = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >}; T = std::basic_string<char>]
test.cpp:15:15: note:   no known conversion for argument 1 from ‘<unresolved overloaded function type>’ to ‘std::basic_string<char>& (std::basic_string<char>::*)(std::basic_string<char>)’
test.cpp: In member function ‘Wrapper<T>& Wrapper<T>::operator=(Args&& ...) [with Args = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >}; T = std::basic_string<char>]’:
test.cpp:11:3: warning: control reaches end of non-void function [-Wreturn-type]

Line 26 is w = std::string("???"); and line 15 is the declaration of this_member_fn, so it seems that the type the compiler thinks func (=std::string::operator=) has is not the one it was expecting.

Is there a way to do this using a templated operator= like I am, rather than overriding each operator= in the base class individually?

uckelman
  • 25,298
  • 8
  • 64
  • 82

3 Answers3

4

There's no need to take the address of a member if you intend to use it on the spot. This spares you the problem of finding which overloaded version to pick, too.

template<
    typename U
    , typename std::enable_if<
        std::is_assignable<T&, U>::value
        , int
    >::type = 0
>
Wrapper& operator=(U&& u)
{
    static_cast<T&>(*this) = std::forward<U>(u);
    return *this;
}

The constraint (the SFINAE test via std::enable_if) is highly recommended as otherwise something as simple as Wrapper<int> w, v; w = v; will fail by attempting to assign a Wrapper<int> to an int. With the constraint, the special member Wrapper& operator=(Wrapper const&); will properly be picked.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
Luc Danton
  • 34,649
  • 6
  • 70
  • 114
1

There are several std::string::operator=s, so the expression &T::operator= where T = std::string would not result in a pointer to member function.

K-ballo
  • 80,396
  • 20
  • 159
  • 169
1

No, a function template can never be virtual, so cannot override anything.

But you can have a non-virtual function template (so it doesn't override anything) and it can call the base class function by explicitly-qualifying the name:

this->T::operator=(std::forward<Args>(args)...);
return *this;

(The this-> is actually unnecessary there, but included for clarity.)

This will select the right function by overload resolution, whereas your version with &T::operator= doesn't unambiguously name a single overload, so is not the address of a single function (T::operator= names a whole overload set and overload sets aren't first-class objects in C++ so can't be passed to a function.)

It can't return that expression, because the base class' assignment operator doesn't return the right type. You could use a static_cast<Wrapper&>(...) but it's easier just to return *this.

Community
  • 1
  • 1
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Replacing the body of `Wrapper::operator=` as you suggest *does* work (in fact, it's what I had earlier in the day). But it means that I can't use an auxiliary function to also produce the overloads for other member functions returning `T&`, such as `std::string::assign` and `std::string::replace`, which is what I was hoping to do. – uckelman Jun 25 '12 at 19:11
  • In general that won't work, because if the base function is overloaded then `&T::foo` is ambiguous. You can resolve that via e.g. `static_cast(&T::operator=)` but that requires knowing which overload you want in advance, which isn't going to be very helpful. – Jonathan Wakely Jun 25 '12 at 19:23