3

Is there a way to "bundle" template parameters together to avoid repetition?

I have several classes and functions that all use the same three template parameters. It is not unusual to have a function that uses each class/function once. The resulting code gets very messy very quickly. Is there a more concise way to write this code?

// ContextFactory is a pointer to functions that instantiate objects that are subtypes of MetricContext
template<typename VertexID, typename EdgeMembershipType, typename SetBitmap>
using ContextFactory = MetricContext <VertexID, EdgeMembershipType, SetBitmap> *(*)(const char *);

template<typename VertexID, typename EdgeMembershipType, typename SetBitmap>
    static vector<ContextFactory<VertexID, EdgeMembershipType, SetBitmap>> buildCFList() {
      vector<ContextFactory<VertexID, EdgeMembershipType, SetBitmap>> answer;
      answer.push_back(MetricContext<VertexID, EdgeMembershipType, SetBitmap>::template make<NeoContext<VertexID, EdgeMembershipType, SetBitmap >>);
      return answer;
    };

Notice that almost half of this function is repetition of the string <VertexID, EdgeMembershipType, SetBitmap>>, but each use of this string applies to a different class or function, so I don't think alias will work.

(If it helps, the purpose of this function is to create an array of pointers to functions that will create objects that are subtypes of MetricContext<VertexID, EdgeMembershipType, SetBitmap>>

Quentin
  • 62,093
  • 7
  • 131
  • 191
Zack
  • 6,232
  • 8
  • 38
  • 68
  • With C++11 you could bundle them into a template parameter pack. You lose some readability, though. – AndyG Jan 10 '17 at 15:49
  • To begin with, you can use `auto` for return value. – pSoLT Jan 10 '17 at 15:50
  • FWIW: When I replace the return type with `auto` (`static auto buildCFList()`), I get this error `'auto' return without trailing return type; deduced return types are a C++14 extension` – Zack Jan 10 '17 at 15:58
  • Do you need the alias 'ContextFactory' outside of the static function? If not, you can pull it inside and get rid of one line of template params. – Benjamin Maurer Jan 10 '17 at 16:05
  • The only place I need it outside the static function is in the return type. – Zack Jan 10 '17 at 16:09
  • Well, my solution only works nicely with C++14 and auto return type... – Benjamin Maurer Jan 10 '17 at 16:11
  • A rather cheap insight : use "class" inside of "typename" for template parameters, it is shorter and has the exact same behavior, by design. – Jean-Bernard Jansen Jan 11 '17 at 12:30
  • Are you suggesting this primarily to reduce code length, or is there another benefit? – Zack Jan 11 '17 at 14:30
  • @AndyG Since there is always exactly three parameters, can I use a parameter pack? Or, do parameter packs only work when the number of parameters is arbitrary? – Zack Jan 12 '17 at 22:06
  • @Zack: You can use it in this scenario. It's probably overkill, but it'd save you some typing. `template static vector> buildCFList() { vector> answer; answer.push_back(MetricContext::template make>); return answer; };` – AndyG Jan 13 '17 at 12:38

3 Answers3

5

A rather more specific approach than @Quentin's is to make your template depend on a single parameter - which is expected to have typedefs for VertexID, EdgeMembershipType, and SetBitmap.

// ContextFactory is a pointer to functions that instantiate objects that are 
// subtypes of MetricContext
template<typename Types>
using ContextFactory = MetricContext <Types> *(*)(const char *);

template<typename Types>
    static vector<ContextFactory<Types>> buildCFList() {
      vector<ContextFactory<Types>> answer;
      answer.push_back(MetricContext<Types>::template make<NeoContext<Types>>);
      return answer;
    };

Note that when you want to actually use one of the typedefs, you will need to use for example: typename Types::VertexID.

(Ideally you would come up with a better name than Types for the template argument.)

4

Yes, this is possible. Let's define a little helper class to hold a list of types:

template <class... > struct pack { };

And a metafunction that instantiates a template with what's inside a pack:

template <template <class... > class T, class P>
struct unpack_;

template <template <class... > class T, class... P>
struct unpack_<T, pack<P...>> {
    using type = T<P...>;
};

template <template <class... > class T, class P>
using unpack = typename unpack_<T, P>::type;

And we can now store and use our parameter pack:

template <class A, class B, class C>
struct Foo { };

using Params = pack<int, float, double>;

unpack<Foo, Params> f; // f is a Foo<int, float, double>

See it live on Coliru

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • The downside of this approach is that id does not help to reduce `buildCFList` implementation verbosity since inside the function, the fact that we use a pack is lost. – Jean-Bernard Jansen Jan 11 '17 at 12:15
  • @Jean-BernardJansen the more I look at it, the more I believe I've read the question completely wrong... – Quentin Jan 11 '17 at 12:22
1

if you're using C++11, you can make use of std::tuple to combine the variables into one.

A simpler example to understand the same can be

template <typename A, typename B, typename C>
void fn() {

 typedef std::tuple<A,B,C> myTuple;

  myTuple tpl;
  cout<<sizeof(std::get<0>(tpl))<<endl;;
  cout<<sizeof(std::get<1>(tpl))<<endl;
  cout<<sizeof(std::get<2>(tpl))<<endl;

}

int main() {

 fn<int,char,long>();

 return 0;
}

For the problem specific to your question, you can create vectors of tuple as

template <typename A, typename B, typename C>
void fn() {

    using mycomb = std::tuple<A,B,C>;

    vector<mycomb> v1;
    v1.push_back(make_tuple(10,'c',20.0));
}

In this way, you don't need to repeat the same. Tuple getter functions are bit awkward at first. The cout examples above demonstrates how to access tuple parameters

Hope this helps

Daksh Gupta
  • 7,554
  • 2
  • 25
  • 36