6

As an exercise in understanding C++0x, I am trying to create a C++ class that wraps a pointer of some template-ized type:

template <typename T>
class Wrapper {
    T *t;
    /* ... */
};

Inside of the Wrapper class, I would like to expose any overloaded operators that T may implement through the Wrapper class. The wrapper itself simply forwards the function call to the underlying t object.

template <typename U>
auto operator+(U &u) -> decltype (*t + u) {
    return *t + u;
}

The catch is that I do not want Wrapper exposing operators that T may not implement. For example, if T does not implement operator+ then Wrapper should not expose operator+ as well.

In the case of operator+ (and any binary operation), everything works out because the operator necessarily becomes a template function and is thus only instantiated when we try to invoke, e.g., Wrapper::operator+.

However, in the case of unary operators (e.g., ++), there is not a clear way to guard the operator so that it is instantiated iff T implements operator++. For example, the naive implementation of operator++ in this class

auto operator++() -> decltype(++(*t)) {
    return ++(*t);
}

fails to compile for a T that does not support operator++().

From my understanding of the standard, if we have the following code that uses Wrapper

class X { };
Wrapper<X> w;

We will instantiate Wrapper and the declaration of Wrapper::operator++() but not its definition unless we invoke it (or explicitly instantiate it). Normally this would be ok, because the use of X::operator++ occurs only in the definition of Wrapper::operator++(). However, because of decltype, we use X::operator++ in the declaration so that the typechecker checks for the existence of X::operator++ and thus fails.

Can we define operator++() (and in general any such forwarding function that uses decltype) with the property that it is instantiated iff the underlying object also supports operator++()? Or given the semantics of template instantiation along with decltype, is this impossible to accomplish?

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
psosera
  • 165
  • 4
  • As a related question: is it legal to refer to `this` in a trailing return `decltype`? Visual C++ 2010 and g++ 4.5.1 do not like that. – James McNellis Mar 31 '11 at 17:27
  • @James - see this discussion from a month ago [http://stackoverflow.com/questions/5147492/member-function-call-in-decltype](http://stackoverflow.com/questions/5147492/member-function-call-in-decltype). – Bo Persson Mar 31 '11 at 18:16
  • @BoPersson: Thanks! I guess we'll find out in a couple of weeks whether that change made it into C++0x. – James McNellis Mar 31 '11 at 18:20
  • @James you will be able to use `this` in member functions/member function templates between the optional cv qualifier seq and the end of the definition/declaration. This means it's legal to use it in late specified return types and exception specifications. Implicit this is added at those places, which will properly constify the member references depending on the constness of the object expression. – Johannes Schaub - litb Mar 31 '11 at 18:49

3 Answers3

5

You can declare the operator as a non-member template:

template <typename T>
auto operator++(Wrapper<T>& arg) -> decltype(++*arg.t) {
    return ++*arg.t;
}
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 2
    @james-mcnellis Good suggestion! However, what about operators that must be declared as member functions but do not have any arguments to piggyback a templated type onto? (Of which, I think the only one is operator->()). – psosera Mar 31 '11 at 17:42
  • That's an interesting question, to which I don't have an answer at the moment. – James McNellis Mar 31 '11 at 17:43
  • @user686170 : If you're using GCC and want to get a little creative with `std::enable_if`, I posted an answer that works for e.g. `operator->`. – ildjarn Apr 02 '11 at 04:26
4

You could also do tricks with default template arguments, just for making the operand of the operator be dependent

template<typename Trick = T>
auto operator++() -> decltype(++(static_cast<Trick&>(*t))) {
    return ++(*t);
}

Perhaps with a helper function in between

template<typename /* Ignored */, typename T> T &&id(T &&t) {
    return std::forward<T>(t);
}

template<typename Void = void>
auto operator++() -> decltype(++(*id<Void>(t))) {
    return ++(*t);
}
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
2

If you can figure out how to work std::enable_if into the operator signature, here's a metafunction that checks for the existence of e.g. operator->:

#include <type_traits>

template<typename T, typename R>
inline R* has_deref_opr_sfinae_impl_helper(R (T::*)()) { return 0; }

template<typename T, typename R>
inline R* has_deref_opr_sfinae_impl_helper(R (T::*)() const) { return 0; }

template<
    typename T,
    bool IsPointer =
        std::is_pointer<T>::value &&
        !std::is_same<
            typename std::remove_cv<
                typename std::remove_pointer<
                    typename std::remove_cv<T>::type
                >::type
            >::type,
            void
        >::value
>
class has_deref_opr
{
    template<
        typename U,
        typename R = decltype(has_deref_opr_sfinae_impl_helper(&U::operator->))
    >
    struct sfinae_impl { };

    typedef char true_t;
    struct false_t { true_t f[2]; };

    template<typename U>
    static true_t check(U*, sfinae_impl<U>* = 0);
    template<typename U>
    static false_t check(...);

public:
    static bool const value = sizeof(check<T>(0)) == sizeof(true_t);
};

template<typename T>
class has_deref_opr<T, true>
{
public:
    static bool const value = true;
};

A few notes:

  • I tested with GC 4.4.1, it didn't like has_deref_opr_sfinae_impl_helper being inside of has_deref_opr, not sure why. Maybe this is changed in more recent versions of GCC
  • VC++ 2010 SP1 fails to compile this due to a template instantiation bug that I couldn't find a workaround for :-[

Hope this helps.

ildjarn
  • 62,044
  • 9
  • 127
  • 211