1

I have a templated class that can take either a scalar, or an indexed type, and I would like to call a different version of a function depending on what type it is:

template <typename Ta, typename ... T>
struct MyClass {
    Ta tt; //can be either a scalar (double, complex<double>, int, etc), 
          //or an indexed type (MyClass, std::vector<double>, double[], etc)

    //....

    //I would like the following to be called when tt is a scalar type:
    auto operator[]( size_t i ) { return tt; };

    //I would like the following to be called when tt has [] overloaded:
    auto operator[]( size_t i ) { return tt[i]; };

};

Is there a way to do this? Return value SFINAE doesn't work (because there isn't a template parameter on this function), Class based SFINAE doesn't seem to work (because the variadic template makes having a dummy template parameter at the end not work). Any other ideas?

Andrew Spott
  • 3,457
  • 8
  • 33
  • 59
  • Note that "auto" for a function return type is in C++1y standard. In C++11 you should use syntax like this: "auto operator[]( size_t i ) -> decltype(this->value(0, 0))" (as described above in the answers) – cppist Mar 24 '13 at 15:52

2 Answers2

4

I believe Xeo misunderstood what Andrew meant by "scalar". Xeo is following the C/C++ definition of scalar (as per C++11, 3.9/9) and Andrew meant something closer to the linear algebra sense (an element of the underlying field of a vector space). For instance, while for Andrew std::complex<double> is scalar, this is not the case for Xeo since he uses std::is_scalar<std::complex<double>> to check that and, of course, he gets false.

I believe, what Andrew wants is operator[](size_t i) to return:

  1. tt[i] if tt[i] is legal; or

  2. tt if tt[i] is illegal.

We can easily SFINAE away the first candidate above. By implementing a trait that checks whether calling tt[i] is legal or not, we can also SFINAE away the second candidate when the expression is legal. Creating this trait isn't very straightforward and relies on overload resolution (reall the classic no f(...)). I'll skip the creation of the trait and, instead, use the same ideas directly inside MyClass. To avoid polluting operator[]()'s signature with a dummy argument (needed for the overload resolution trick) I'll create private methods called value() which take the dummy argument. Then operator[]() will just delegate the call to one of them.

The code follows.

#include <type_traits> // for std::declval

template <typename Ta>
class MyClass {

    // This overload is SFINAEd away when tt[i] is illegal
    template <typename T = Ta, typename R = decltype(std::declval<T>()[0])>
    R
    value(size_t i, int) { return tt[i]; }

    // This one is always present but is a worse match than the other
    // one when resolving value(i, 0).
    const Ta&
    value(size_t, ...) { return tt; }

public:

    Ta tt;

    auto operator[]( size_t i ) -> decltype(this->value(0, 0)) {
        return value(i, 0);
    }

};
Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • 1
    A good point about what might be meant with "scalar", +1. I thought about going the "is `tt[i]` valid" route too, but I was too lazy to write what you have. :) Btw, [this](http://stackoverflow.com/a/9154394/500104) may be of interest to you. – Xeo Mar 12 '13 at 16:48
  • Thanks! this is indeed what I was interested in. – Andrew Spott Mar 12 '13 at 19:02
3

Just give the operator[]s a dummy template parameter.

template<class U = Ta, EnableIf<std::is_scalar<U>>...>
auto operator[]( size_t i ){ return tt; };

template<class U = Ta, DisableIf<std::is_scalar<U>>...>
auto operator[]( size_t i ) { return tt[i]; };

See here for some explanation about this particular style of SFINAE.

Xeo
  • 129,499
  • 52
  • 291
  • 397