2

How can I factor the following code, so that I can do loop through T = double and T = float? I have read about variadic templates but I don't understand how to apply it in this case:

int main(int argc, char* argv[])
{
  ofstream writeDat;
  vector<int> nValues = {26,51,101,201};
  for(int i = 0; i< 4; i++){
    int N = nValues[i];
    typedef float T ;
    Matrix<T> a(N,N);
    Matrix<T> b(N,3);
    Matrix<T> x = Problem2<T>(N);
    string sFloat = "2/" + to_string(N) + "Float"+".dat";
    writeDat.open(sFloat);
    for(int i =1; i<N ; i++)
      writeDat << i << " " << x(i,1)<<endl;
    writeDat << N <<" "<< x(N,1)<< endl;
    writeDat.close();
  }
  for(int i = 0; i< 4; i++){
    int N = nValues[i];
    typedef double T ;
    Matrix<T> a(N,N);
    Matrix<T> b(N,3);
    Matrix<T> x = Problem2<T>(N);
    string s = "2/" + to_string(N) + "Double"+".dat";
    writeDat.open(s);
    for(int i =1; i<N ; i++)
      writeDat << i << " " << x(i,1)<<endl;
    writeDat << N <<" "<< x(N,1)<< endl;
    writeDat.close();
  }
  return 0;
}
leiyc
  • 903
  • 11
  • 23
georg
  • 305
  • 1
  • 4
  • 12

2 Answers2

2

Use variadic expansion to call a template function (or variadic lambda) containing your duplicated logic:

#include<fstream>
#include<vector>

// the concept of a type wrapper
template<class T> struct type_wrapper;

// a model of type_wrapper for floats
template<>
struct type_wrapper<float> { 
    using type = float; 
    constexpr const char* name() const { return "Float"; }
};

// a model of type_wrapper for doubles
template<>
struct type_wrapper<double> { 
    using type = double; 
    constexpr const char* name() const { return "Double"; }
};


// call a template function once for each type wrapper in Ts...
template<class...Ts, class F>
auto for_each_type(F&& f)
{
    (f(type_wrapper<Ts>()),...);
}

template<class T>
struct Matrix
{
    Matrix(int, int);
    T& operator()(int, int);
};

template<class T> Matrix<T> Problem2(int);

int main()
{
    auto process = [](auto twrap) {
        using T = typename decltype(twrap)::type;
        std::ofstream writeDat;
        std::vector<int> nValues = {26,51,101,201};
        for(int i = 0; i< 4; i++){
            int N = nValues[i];
            Matrix<T> a(N,N);
            Matrix<T> b(N,3);
            Matrix<T> x = Problem2<T>(N);
            std::string sFloat = "2/" + std::to_string(N) + twrap.name() + ".dat";
            writeDat.open(sFloat);
            for(int i =1; i<N ; i++)
            writeDat << i << " " << x(i,1)<<std::endl;
            writeDat << N <<" "<< x(N,1)<< std::endl;
            writeDat.close();
        }
    };

    for_each_type<double, float>(process);
}

https://godbolt.org/z/w6g6AC

Note:

You can make for_each_type more portable (i.e. work on c++14) like this:

template<class...Ts, class F>
auto for_each_type(F&& f)
{
#if __cplusplus >= 201703L
    (f(type_wrapper<Ts>()),...);
#else
    using expand = int[];
    expand {
        0,
        (f(type_wrapper<Ts>()), 0)...
    };
#endif
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • This is really nice, I didn't see through the OP's intentions all the way through like you did. It says explicitly to be looping through the types.. Removing my answer : ) – Geezer Sep 24 '18 at 08:13
  • 1
    @SkepticalEmpiricist Thank you. This technique was brought to my attention by studying the boost::hana library. – Richard Hodges Sep 24 '18 at 08:18
  • Of course, I clearly see that now. Thank you for that. – Geezer Sep 24 '18 at 08:20
  • 1
    I have edited and un-deleted to refer readers to your fine answer. – Geezer Sep 24 '18 at 08:26
1

I have read about variadic templates but I don't understand how to apply it in this case:

I believe what you are asking is answered most adequately by @RichardHodges. For reference sake, in case you want to be able to compare doing this the variadic template (since C++11) or fold expression (since C++17) way with doing this the function template only way (not that there's a special reason to do so), then you could use this snippet for example:

#include <vector>
#include <fstream>

using namespace std;

// Undefined struct 
template <typename T> struct not_available;

// Non-specialized instantiations are not allowed, by using the undefined struct
template <typename T>
constexpr const char* type_string(){ return not_available<T>{}; }

// Specializing for `float`
template <>
constexpr const char* type_string<float>(){ return "Float"; }

// Specializing for `Double`
template <>
constexpr const char* type_string<double>(){ return "Double"; }

// Your classes
template<class T>
struct Matrix
{
    Matrix(int, int);
    T& operator()(int, int);
};

template<class T> Matrix<T> Problem2(int);

ofstream writeDat;
vector<int> nValues = {26,51,101,201};

// Your routine
template <typename T>
void func()
{  
    for(int i = 0; i< 4; i++){
    int N = nValues[i];    
    Matrix<T> a(N,N);
    Matrix<T> b(N,3);
    Matrix<T> x = Problem2<T>(N);
    string s = "2/" + to_string(N) + type_string<T>() +".dat";
    writeDat.open(s);
    for(int i =1; i<N ; i++)
      writeDat << i << " " << x(i,1)<<endl;
    writeDat << N <<" "<< x(N,1)<< endl;
    writeDat.close();
    }
}

int main(int argc, char* argv[])
{
    func<float>();
    func<double>();

    return 0;
}

Note: This snippet tries to stick as much as possible to your original, but the absence of good enough reason I wouldn't necessarily advise using global variables, neither opting for using namespace std instead of referring to names with std::.

Geezer
  • 5,600
  • 18
  • 31