1

I've made a function that calculates the sine of a number. It returns the input type if it is std::is_floating_point. But for std::is_integral, it returns a double.

template<class T , typename std::enable_if<std::is_integral<T>::value>::type* = nullptr >
double mysin(const T& t) // note, function signature is unmodified
{
    double a = t;
    return std::sin(a);
}

template<class T , typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr >
T mysin(const T& t) // note, function signature is unmodified
{
    return std::sin(t);
}

Easy enough. Now I'd like this to work for vectors (or arrays) and tuples (or clusters). So that:

(pseudo code:)
std::vector<std::double> a = mysin(std::vector<std::int>); 
std::tuple<std::double, std::float> b = mysin(std::tuple<std::int, std::float>);
std::vector<std::tuple<std::double, std::float>> c = mysin(std::vector<std::tuple<std::int, std::float>>);
std::tuple<std::vector<std::double>, std::float> d = mysin(std::tuple<std::vector<std::int>, std::float>);
std::tuple<std::tuple<std::double, std::vector<std::double>>, std::float>> e = mysin(std::tuple<std::tuple<std::int, std::vector<std::int>>, std::float>>);
and so on...

In most examples about tuple templates, the function either has no return value, returns an accumulated value, or has the same return type as the input.

I've experimented a lot with these topics (among others): Traversing nested C++11 tuple , c++11: building a std::tuple from a template function , How to make a function that zips two tuples in C++11 (STL)?

The last one has been especially useful. I've got this working for tuples, but not for recursive tuples (tuples in tuples).

Eventually (if this is possible at all), I'll have to make a mycos, mytan, myasin, etc. Using gcc 4.9.2.

**EDIT:**So this is what I came up with after Yakk's suggestions, and a little tweaking:

#include <utility>
#include <vector>
#include <memory>
#include <typeinfo> // used for typeid
#include <tuple>
#include <cstdlib> // for math functions?
#include <cmath> // for math functions
#include <type_traits> // for std::enable_if

template<class T , typename std::enable_if<std::is_integral<T>::value>::type* = nullptr >
double mysin(const T& t) { // note, function signature is unmodified
    double a = t;
    return std::sin(a);
// printing a debug string here will
// print tuple elements reversed!!
}


template<class T , typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr >
T mysin(const T& t) {// note, function signature is unmodified
// printing a debug string here will
// print tuple elements reversed!!
    return std::sin(t);
}

struct sine_t {
    template<class T>
    auto operator()(T&&t)const->
        decltype(mysin(std::declval<T>())) {
            return mysin(std::forward<T>(t));
            }
};

template<class F>
struct vectorize {
    template<class T,
        class R=std::vector< std::result_of_t< vectorize<F>(T const&) > >
    >
        R operator()( std::vector<T> const& v ) const {
            R ret;
            ret.reserve(v.size());
            for( auto const& e : v ) {
                ret.push_back( vectorize<F>{}(e) );
                }
        return ret;
        }

    template<
        class X,
        class R=std::result_of_t< F(X const&) >
    >
        R operator()( X const& x ) const {
            return F{}(x);
            }   

    template<
        class R, 
        class... Ts, 
        size_t... Is
    >
    R tup_help( std::index_sequence<Is...>, std::tuple<Ts...> const& t ) const {
        return std::make_tuple( vectorize<F>{}(std::get<Is>(t))... );
        }

    template<
        class... Ts,
        class R=std::tuple< std::result_of_t< vectorize<F>(Ts const&) >... >
    >
    R operator()( std::tuple<Ts...> const& t ) const {
        return tup_help<R>( std::index_sequence_for<Ts...>{}, t );
        }

    };

//+++++++++++++++++++++++++++++++++++++++++

int main() {
    std::vector<int> a = {1 ,2};
    std::tuple<int, double, int, double> b (42, -3.14, 42, -3.14);

    auto c = vectorize<sine_t>()(a);
    auto d = vectorize<sine_t>()(b);

    std::vector<std::tuple<int, int> > e {std::make_tuple(1 ,2)};
    //This does not not work:
    //auto f = vectorize<sine_t>()(e);

    //This works:
    std::tuple<std::vector<int> > g ( a );
    auto f = vectorize<sine_t>()(g);

    return 0;
}

This works. Needs c++14.

Community
  • 1
  • 1
Wiebe
  • 87
  • 6

1 Answers1

1

First an overload set object. This is useful because it lets us pass around the entire overload set as a single object:

struct sine_t {
  template<class T>
  auto operator()(T&&t)const->
  decltype(mysin(std::declval<T>()))
  { return mysin(std::forward<T>(t)); }
};

next, we want to "vectorize" a given function object.

We'll start simple:

template<class F>
struct vectorize {
  template<class T, class R=std::vector< std::result_of_t< F(T const&) > >>
  R operator()( std::vector<T> const& v ) const {
    R ret;
    ret.reserve(v.size());
    for( auto const& e : v ) {
      ret.push_back( F{}(e) );
    }
    return ret;
  }
  template<class X, class R=std::result_of_t< F(X const&) >>
  R operator()( X const& x ) const {
    return F{}(x);
  }
};

this supports 1 level recursion, and only on std::vector.

To allow infinite recursion of nested std::vectors, we modify the operator() overload for std::vector:

  template<
    class T,
    class R=std::vector< std::result_of_t< vectorize<F>(T const&) > >
  >
  R operator()( std::vector<T> const& v ) const {
    R ret;
    ret.reserve(v.size());
    for( auto const& e : v ) {
      ret.push_back( vectorize<F>{}(e) );
    }
    return ret;
  }

and now we support std::vector<std::vector<int>>.

For tuple support, we add 2 functions. The first one has this signature:

  template<
    class... Ts,
    class R=std::tuple< std::result_of_t< vectorize<F>(Ts const&) >... >
  >
  R operator()( std::tuple<Ts...> const& t ) const

which gets us our return value (half the battle). To actually do the mapping, use the indexes trick:

  template<
    class R,
    class... Ts,
    size_t... Is
  >
  R tup_help( std::index_sequence<Is...>, std::tuple<Ts...> const& t ) const
  {
    return std::make_tuple( vectorize<F>{}(std::get<Is>(t))... );
  }
  template<
    class... Ts,
    class R=std::tuple< std::result_of_t< vectorize<F>(Ts const&) >... >
  >
  R operator()( std::tuple<Ts...> const& t ) const {
    return tup_help<R>( std::index_sequence_for<Ts...>{}, t );
  }

similar code for std::array and raw C arrays should work (converting the raw C array to a std::array naturally).

std::index_sequence etc is C++14, but easy to write a version that supports 100s of elements in C++11. (A version that supports large arrays takes more work). The result_of_t alias (and any similar) are also C++14, but the aliases are easy to write in C++11, or you can just typename std::result_of<?>::type verbose explosion.

In the end, you vectorize<sine_t>{} and pass in whatever.

If you want a function instead of a function object, simply have it delegate the work to vectorize<sine_t>.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • @wiebe yes, `vectorize{}(e)` I will try to fix typos that break the call later today: debugging SFINAE errors is tricky sometimes. The idea is that `sine_t` calls a bunch of overloads of a free function is useful. You can gice it a different name (`my_sin_element`), thrn write `my_sine` as calling `vectorize{}(t)`. – Yakk - Adam Nevraumont Mar 13 '15 at 10:42
  • don't know why I got to see your comment only after I deleted my comment today... Anyway, you've got me to 95%, and the other 5% was a good learning experience. Note the "s" behind std::index_sequences. It's the only mistake, otherwise your code was perfect, I just didn't understand the vectorize{}(t) syntax. Is it better then vectorize()(t) ? Thanks. – Wiebe Mar 19 '15 at 14:37
  • @Wiebe It makes it clear that I am constructing an object, as opposed to calling a function. That is about it. – Yakk - Adam Nevraumont Mar 19 '15 at 14:47
  • Hmm. Have to read Item 7, Effective Modern C++. It's about () vs. {}. Still at Item 3... By the way, it does not work for tuple vectors (vector > ). Compiler says there's no template. Tuples, vectors and integrals work fine. Vector tuples works as well. – Wiebe Mar 20 '15 at 08:40
  • Ok, fixed the tuple vector problem. class R=std::vector< std::result_of_t< F(T const&) > > should be class R=std::vector< std::result_of_t< vectorize(T const&) > >, or it won't recurse the vector type. – Wiebe Mar 28 '15 at 17:11
  • @Wiebe Yes. Was that an error I have above that I missed, or was it an error in your version of the code? – Yakk - Adam Nevraumont Mar 28 '15 at 20:35
  • \@Yakk. Don't know. I think I mostly copy-pasted that part. Anyway it's working! I made more changed as my understanding of it grows. I've put the double\integers construction inside the struct as well. This makes it less flexible, but since I want all math functions (sin, cos etc.) to behave like this, it makes sense to reuse the code. I also adjusted the templates to accept more then one parameter. But the vectorization is only done on the first parameter. Thanks again for the help. – Wiebe Mar 30 '15 at 10:18