【问题标题】:Dynamic dispatching of template functions?模板函数的动态调度?
【发布时间】:2011-08-17 07:34:11
【问题描述】:

是否可以在运行时决定调用哪个模板函数? 比如:

template<int I>
struct A {
    static void foo() {/*...*/}
};

void bar(int i) {
    A<i>::f();   // <-- ???
}

【问题讨论】:

    标签: c++ templates c++11


    【解决方案1】:

    在处理模板时连接编译时间和运行时的典型“技巧”是访问变体类型。例如,这就是通用图像库(可作为 Boost.GIL 或独立的)所做的。它通常采用以下形式:

    typedef boost::variant<T, U, V> variant_type;
    variant_type variant = /* type is picked at runtime */
    boost::apply_visitor(visitor(), variant);
    

    其中visitor 是一个简单地转发到模板的多态函子:

    struct visitor: boost::static_visitor<> {
        template<typename T>
        void
        operator()(T const& t) const
        { foo(t); } // the real work is in template<typename T> void foo(T const&);
    };
    

    这有一个很好的设计,模板将/可以实例化的类型列表(这里是variant_type 类型的同义词)不与其余代码耦合。像boost::make_variant_over 这样的元函数还允许对要使用的类型列表进行计算。

    由于此技术不适用于非类型参数,因此您需要手动“展开”访问,不幸的是,这意味着代码不具有可读性/可维护性。

    void
    bar(int i) {
        switch(i) {
            case 0: A<0>::f(); break;
            case 1: A<1>::f(); break;
            case 2: A<2>::f(); break;
    
            default:
                // handle
        }
    }
    

    处理上述开关中的重复的通常方法是(ab)使用预处理器。使用 Boost.Preprocessor 的(未经测试的)示例:

    #ifndef LIMIT
     #define LIMIT 20 // 'reasonable' default if nothing is supplied at build time
    #endif
    #define PASTE(rep, n, _) case n: A< n >::f(); break;
    
    void
    bar(int i) {
        switch(i) {
            BOOST_PP_REPEAT(LIMIT, PASTE, _)
    
            default:
                // handle
        }
    }
    
    #undef PASTE
    #undef LIMIT
    

    最好为 LIMIT 找到良好的、自我记录的名称(PASTE 也不会受到伤害),并将上述代码生成限制在一个站点。


    从 David 的解决方案和您的 cmets 构建:

    template<int... Indices>
    struct indices {
        typedef indices<Indices..., sizeof...(Indices)> next;
    };
    
    template<int N>
    struct build_indices {
        typedef typename build_indices<N - 1>::type::next type;
    };
    
    template<>
    struct build_indices<0> {
        typedef indices<> type;
    };
    
    template<int... Indices>
    void
    bar(int i, indices<Indices...>)
    {
        static void (*lookup[])() = { &A<Indices>::f... };
        lookup[i]();
    }
    

    然后调用barbar(i, typename build_indices&lt;N&gt;::type()),其中N 将是您的常数时间常数sizeof...(something)。您可以添加一个图层来隐藏该调用的“丑陋”:

    template<int N>
    void
    bar(int i)
    { bar(i, typename build_indices<N>::type()); }
    

    称为bar&lt;N&gt;(i)

    【讨论】:

    • 如果我知道(在编译时)有多少个案例,我可以让编译器为我展开它吗?
    • 很抱歉让我感到痛苦......但我对病例数量的了解来自size...() 运算符。恐怕预处理器在这种情况下无济于事。你能想出一些可以帮助我克服这种限制的方法吗?
    • @Predrag 选择一个足够大并且您觉得合适的上限。
    • 对上一条评论的更正:是sizeof...()运营商。
    • 哇!我终于理解了 indices/build_indices 代码。这是“如何构建参数包”问题的答案,我从未问过但打算这样做。谢谢。
    【解决方案2】:

    具体取决于您想要做什么(即是否有少量的有限实例需要使用?)您可以创建一个查找表,然后动态使用它。对于使用选项 0、1、2 和 3 的完全手动方法,您可以这样做:

    void bar( int i ) {
       static void (*lookup[])(void) = { &A<0>::foo, &A<1>::foo, &A<2>::foo, &A<3>::foo };
       lookup[i]();
    }
    

    当然,我为示例选择了最简单的选项。如果您需要的数字不是连续的或从零开始的,您可能更喜欢std::map&lt;int, void (*)(void) &gt; 而不是数组。如果您要使用的不同选项的数量较大,您可能希望添加代码以自动实例化模板,而不是手动输入所有模板......但是您必须考虑模板的每个实例化都会创建一个新的函数,你可能想检查你是否真的需要它。

    编辑:我写了一个post,仅使用 C++03 功能实现了相同的初始化,答案似乎太长了。

    Luc Danton 写了一个有趣的答案here,其中包括使用 C++0x 构造初始化查找表。我不太喜欢该解决方案,因为它会更改接口以需要额外的参数,但这可以通过中间调度程序轻松解决。

    【讨论】:

    • 这就是我想要的。你能告诉我如何自动初始化数组或映射(或引导我到某个地方)吗?
    • @Predrag:你想要多少个不同的值?它们是连续的吗?从零开始?另请注意,每次实例化都会创建一个新函数,这反过来意味着它将使您的二进制文件增长...
    • 它们是从零开始的,我在编译时就知道它们中有多少存在。
    • 您会考虑将我的(最新)编辑移至您的答案吗?这是一种从编译时常量初始化lookup 的方法。我认为它属于你的答案,它是关于一个查找表,而不是我的,它提到了一个 switch 语句。而且评论太长了。
    • @Luc:我希望你不要介意,而不是借用我链接到你的实现。希望您能得到应得的荣誉。
    【解决方案3】:

    不,模板是编译时的特性,i 在编译时是未知的,所以这是不可能的。 A&lt;I&gt;::foo() 应该适应 A::foo(i) 之类的东西。

    【讨论】:

    • 我知道这一点。如果可能有一些解决方法,我会徘徊。
    • 解决方法是在编译时使i 已知(如@iammilind 的解决方案),或者使A::foo 不需要编译时参数(我的解决方案)。
    • 还有第三种方法(如果选择数量有限):通过在编译时实例化所有函数来构建查找表,然后在运行时分派给其中一个。我以前用过,很痛苦,但可行。
    【解决方案4】:


    模板实现编译时多态而不是运行时多态。

    【讨论】:

      【解决方案5】:

      模板参数必须在编译时已知。所以编译器没有办法通过A&lt;i&gt;::foo()

      如果您想解决问题,那么您必须将bar() 也设为template

      template<int i>
      void bar() {
          A<i>::f();   // ok
      }
      

      为此,您必须在编译时知道bar() 的参数。

      【讨论】:

        猜你喜欢
        • 2010-10-08
        • 1970-01-01
        • 1970-01-01
        • 2019-04-18
        • 1970-01-01
        • 2020-09-30
        • 1970-01-01
        • 2017-06-10
        • 2020-03-11
        相关资源
        最近更新 更多