Search code examples
c++templatestemplate-templates

trouble with specialising template template parameters


I often use the following construction for converting run-time (dynamic) arguments into compile-time (static) arguments

namespace Foo {
  enum struct option { A,B,C,D,E,F };

  template<template<option> class Func, typename... Args>
  auto Switch(option opt, Args&&...args)
  -> decltype(Func<option::A>::act(std::forward<Args>(args)...))
  {
    switch(opt) {
    case option::A : return Func<option::A>::act(std::forward<Args>(args)...);
    case option::B : return Func<option::B>::act(std::forward<Args>(args)...);
    // etc.
  }
}

For example

template<Foo::option>
void compile_time(std::string const&);         // given

namespace {
  template<Foo::option Opt>
  struct Helper {
    static void act(std::string const&str) { compile_time<Opt>(str); }
  };
}

void run_time_arg(Foo::option opt, std::string const&str)
{
  Switch<CompileTimeArg>(opt,str);
}

So far so good. But now I have another template argument and want also blah() to have that same template argument. That is, conceptually I want to

template<int, Foo::option>
void compile_time(std::string const&);         // given

namespace {
  template<int Bar, Foo::option Opt>
  struct Helper {
    static void act(std::string const&str) { compile_time<Bar,Opt>(str); }
  };
}

template<int Bar>
void blah(Foo::option opt, std::string const&str)
{
  template<Foo::option Opt> using BarHelper = Helper<Bar,Opt>;
  Switch<BarHelper>(opt, str);
}

but, of course, that is not allowed (a template within block scope in function blah()). So what is the correct solution?

Note that I can put everything within an auxiliary class template

namespace {
  template<int Bar>
  struct Auxiliary
  {
    template<Foo::option Opt> using BarHelper = Helper<Bar,Opt>;
    static void blah(Foo::option opt, std::string const&str)
    { Switch<BarHelper>(opt, str); }
  };
}

template<int Bar>
void blah(Foo::option opt, std::string const&str)
{ Auxiliary<Bar>::blah(opt, str); }

But that's clumsy and unsatisfying. Is there an alternative or better solution? I tried this:

template<typename X, typename Y, X x, template<X,Y> class C>
struct specialise {
  template<Y y> using special = C<x,y>;
};

template<int Bar>
void blah(Foo::option opt, std::string const&str)
{
  using Aux = specialise<int, Foo::option, Bar, Helper>
  Switch<Aux::special>(opt, str); }
}

but gcc (5.1.0) complains that S::special is parsed as a non-type while instantiation yields a type ... which is wrong (I think): instantiation yields a template (anyway inserting typename as suggested doesn't help). So what's wrong and/or how to do this correct/better?


Solution

  • The keyword to add is not typename as it is not a type, but template.

    So, the call should be

    template<int Bar>
    void blah(Foo::option opt, std::string const& str)
    {
        using Aux = specialise<int, Foo::option, Bar, Helper>
        Switch<Aux::template special>(foo, ptr, str);
    }
    

    Live Demo