根据运行时参数调用模板函数的不同版本

时间:2018-03-09 07:50:42

标签: c++

我有几个模板函数,其模板参数总是属于同一个枚举,并且希望根据该枚举中变量的运行时值来调用各种实例:

#include <iostream>

enum Number {One, Two};

template<Number N>
void g(int m) {
  std::cout << N << " " << m << std::endl;
}

int main() {

  auto n = Number::One;
  int m = 0;

  // I want to use this sort of construct for many different functions g
  if (n == Number::One) {
    g<Number::One>(m);
  }
  else if (n == Number::Two) {
    g<Number::Two>(m);
  }
}

我不是每次都写分支,而是想重构它 - 这是我目前的尝试:

struct h {
  template<Number N>
  void operator()(int m) const {
    g<N>(m);
  }
};

template <typename F, typename... Args>
void split(F&& f, Number n, Args&&... args) {
  if (n == Number::One) {
    f.template operator()<Number::One>(args...);
  }
  else if (n == Number::Two) {
    f.template operator()<Number::Two>(args...);
  }
}

int main() {
  split(h{}, n, m);
}

这是实现这一目标的最简洁方法吗?特别是,是否可以重写split以使其直接接受g,而不需要包含结构h

3 个答案:

答案 0 :(得分:3)

你必须将你的函数包装在某些东西中,但我们可以使用lambda而不是类。

如果您的枚举值是连续的,请使用:

(下面的代码依赖于连续的枚举值。它允许我们使用函数指针数组进行快速调度。如果你的枚举不是连续的,请阅读下面的内容。)

#include <iostream>    
#include <utility>    

enum Number {One, Two, NumberCount};

template <Number N> void g(int m)
{
    std::cout << N << " " << m << std::endl;
}

template <typename L, std::size_t ...I>
const auto &split_impl(std::index_sequence<I...>, L lambda)
{
    static decltype(lambda(std::integral_constant<Number, One>{})) array[] =
    {lambda(std::integral_constant<Number, Number(I)>{})...};
    return array;
}
template <typename L, typename ...P> void split(L lambda, Number n, P &&... p)
{
    split_impl(std::make_index_sequence<NumberCount>{}, lambda)[n](std::forward<P>(p)...);
}

int main()
{
    auto wrapped_g = [](auto i){return g<i.value>;};
    split(wrapped_g, One, 42);
}

如果您的枚举值不连续,请使用:

#include <iostream>    
#include <utility>    

enum Number {One, Two};

template <Number N> void g(int m)
{
    std::cout << N << " " << m << std::endl;
}

template <Number ...I, typename L> auto split_impl(L lambda, Number n)
{
    decltype(lambda(std::integral_constant<Number, One>{})) ret = 0;
    ((I == n ? (ret = lambda(std::integral_constant<Number, Number(I)>{}), 0) : 1) && ...);
    return ret;
}
template <typename L, typename ...P> void split(L lambda, Number n, P &&... p)
{
    split_impl<One, Two>(lambda, n)(std::forward<P>(p)...);
    //         ^~~~~~~~
    // List all your enum values here
}

int main()
{
    auto wrapped_g = [](auto i){return g<i.value>;};
    split(wrapped_g, One, 42);
}

P.S。为简单起见,我使用了std::size_tstd::index_sequence。如果您需要健壮性,则应使用std::integral_sequence<std::underlying_type_t<MyEnum>>

答案 1 :(得分:2)

从第一个例子

#include <iostream>

enum Number {One, Two};

template<Number N>
void g(int m) {
  std::cout << N << " " << m << std::endl;
}

int main() {

  auto n = Number::One;
  int m = 0;

  // I want to use this sort of construct for many different functions g
  if (n == Number::One) {
    g<Number::One>(m);
  }
  else if (n == Number::Two) {
    g<Number::Two>(m);
  }
}

我假设您基本上希望根据运行时值g<N>调度到模板特化n。所以基本上你想要用手写的if-else - 级联或带有一些集中调度逻辑的开关来代替。

The following code遍历枚举值并调用与函数参数g<N>匹配的函数m。它仅在枚举是连续的并且定义迭代的结束值时才起作用。这至少集中了调度,如果枚举被修改,调度将自动运行。

 #include <functional>
#include <iostream>

enum class Number {One, Two, Three, MAX};

template<Number N>
void g(int m) {
  std::cout << static_cast<int>(N) << " " << m << std::endl;
}

template<Number N>
struct Dispatch {
    void call(Number n, int m) {
        if (N == n) {
            g<N>(m);
        } else if (N != Number::MAX) {
            Dispatch< static_cast<Number>(static_cast<int>(N)+1) > next;
            next.call(n, m);
        } 
    }
};

template<>
struct Dispatch<Number::MAX> {
    void call(Number, int) {
        throw "Ohje";
    }
};

void dispatch(Number n, int m) {
    Dispatch<Number::One> d;
    d.call(n, m);
}

int main(int argc, char* argv[]) {
    std::cout << argc << std::endl;
    dispatch(static_cast<Number>(argc-2), 42);
    return 0;
}

You can further generalize the code to use it with different functions for g.我将函数调用包装到helper结构中以表示自由函数g<N>。如果Nm与`d

匹配,我们可以将函数传递给调用
#include <functional>
#include <iostream>

enum class Number {One, Two, Three, MAX};

template<Number N>
void g(int m) {
  std::cout << "g" << static_cast<int>(N) << " " << m << std::endl;
}

template<Number N> struct  S {
    void operator()(int m) {
        std::cout << "S" << std::endl;
        g<N>(m);
    }
};

template<Number N> struct  G {
    void operator()(int m) {
        std::cout << "G" << std::endl;
        g<N>(m);
    }
};

template<template<Number> typename C, Number N>
struct Dispatch {
    void call(Number n, int m) {
        if (N == n) {
            C<N> c;
            c(m);
        } else if (N != Number::MAX) {
            Dispatch< C, static_cast<Number>(static_cast<int>(N)+1) > next;
            next.call(n,m);
        } 
    }
};

template<template<Number> typename C>
struct Dispatch<C, Number::MAX> {
    void call(Number, int) {
        throw "Ohje";
    }
};

template<template<Number> typename C>
void dispatch(Number n, int m) {
    Dispatch<C, Number::One> d;
    d.call(n,m);
}

int main(int argc, char* argv[]) {
    std::cout << argc << std::endl;
    dispatch<S>(static_cast<Number>(argc-2), 42);
    dispatch<G>(static_cast<Number>(argc-2), 23);
    return 0;
}

答案 2 :(得分:1)

g的定义方式进行一些修改是可能的。 特别是,我认为在类型系统中表示模板函数是不可能的,但是可以表示模板类/结构。所以,我将g更改为具有静态成员函数的结构。

#include <iostream>

enum Number { One, Two };

template <Number N>
struct g
{
  static void call(int m)
  {
    std::cout << N << " " << m << std::endl;
  }
};

template <template<Number> class F, class R = void>
struct Wrap
{
  template <class... Args>
  static R call(Number n, Args&&... args)
  {
    switch (n) {
      case One: return F<One>::call(std::forward<Args>(args)...);
      case Two: return F<Two>::call(std::forward<Args>(args)...);
    }
  }
};

int main()
{
  auto n = Number::One;
  int m = 0;

  Wrap<g>::call(n, m);
}