mark
mark

Reputation: 62774

Is it possible to base the template specialization on the presence or absence of a certain argument in the method signature?

Consider the following class:

class MyClass
{
public:
  template<class T> typename T::result_type apply(T& func)
  {
    if (is_int())
    {
      return func(int(0));
    }
    return func(double(0));
  }
  ...
};

(The code does not look terribly useful, but it is only a contrived sample to demonstrate my point)

Anyway, a typical functor would be something like this:

struct MyFunc
{
  typedef void result_type;
  template<class V> void operator()(V)
  {
    // do something
  }
};

And one would use it like so:

MyClass c;
MyFunc f;
c.apply(f);

My question is this - can MyClass::apply be changed to recognize a slightly different version of the functor in addition to the original one, for instance the one expecting the caller object reference to be passed along with all the other parameters, something like this:

struct MyFuncEx
{
  typedef void result_type;
  template<class V> void operator()(const MyClass& caller, V)
  {
    // do something
  }
};

So, the following code would compile too:

MyClass c;
MyFunc f;
c.apply(f);
MyFuncEx f2;
c.apply(f2);

As a bonus I would like the compilation to fail if the functor contains both overloads, i.e. the following should fail the compilation:

struct MyFuncSmartAss
{
  typedef void result_type;
  template<class V> void operator()(V)
  {
    // do something
  }
  template<class V> void operator()(const MyClass& caller, V)
  {
    // do something
  }
};

...

MyClass c;
c.apply(MyFuncSmartAss());

But, it is not that important as long as the longer overload takes the precedence over the shorter one.

Upvotes: 3

Views: 142

Answers (1)

Matthieu M.
Matthieu M.

Reputation: 299910

It really depends whether you have C++11 or not. This should solve the overload issue in C++11 (*):

class MyClass {
private:
  int _int;
  double _double;

public:
  template <typename F>
  auto apply(F& f) -> decltype(f(_int), f(_double)) {
    if (is_int()) { return f(_int); }
    return f(_double);
  }

  template <typename F>
  auto apply(F& f) -> decltype(f(*this, _int), f(*this, _double)) {
    if (is_int()) { return f(*this, _double); }
    return f(_double)
  }

};

How does it work ?

  • Removing unsuitable overloads: the trailing-return-type specification with decltype creates an unevaluated context. The compiler performs regular overload resolution of the expression but only cares about the type. If an error occurs (the operator() does not exist in f), then we hit SFINAE and this overload of apply is discarded
  • Ambiguity if both work: if both overloads are suitable (because F provides both operators) then the call is ambiguous

(*) I am not too sure about the correctness of the trailing-return-type specification, and more specifically the use of this and _arg. Both Clang 3.0 and gcc 4.5.2 error out. This can be worked around, just get a bit more verbose.

// first
decltype(f(0), f(0.0))

// second
decltype(f(std::declval<MyClass>(), 0), f(std::declval<MyClass>(), 0.0))

In action at ideone with this workaround:

EDIT: to cope with the int/double requirement.

Upvotes: 3

Related Questions