带有模板参数的C++函数调度

Spi*_*ire 5 c++ templates dispatch

我正在重构一个大类 - 让我们称之为Big- 它有大量的复制粘贴代码.这些复制粘贴代码中switch case的大部分存在于s中,其中只涉及的类型最终不同.代码基于enum类的成员变量进行切换,其值仅在运行时已知.

我尝试解决这个问题涉及到一个Dispatcher类,它通过一个static名为的函数查找适当类型的函数lookup().始终调用执行实际工作的函数,go()并且必须在包装器类模板中定义它们(其唯一参数是enum当前正在打开的运行时值).这些go()功能可能是也可能不是模板功能本身.

这是代码的提炼版本.我对这个问题表示道歉,但这一点很短,我没有失去重要的背景.

#include <cassert>

class Big
{
    public:

        enum RuntimeValue { a, b };

        Big(RuntimeValue rv) : _rv(rv) { }

        bool equals(int i1, int i2)
        {
            return Dispatcher<Equals, bool(int, int)>::lookup(_rv)(i1, i2);
        }

        template<typename T>
        bool isConvertibleTo(int i)
        {
            return Dispatcher<IsConvertibleTo, bool(int)>::lookup<T>(_rv)(i);
        }

    private:

        template<RuntimeValue RV>
        struct Equals
        {
            static bool go(int i1, int i2)
            {
                // Pretend that this is some complicated code that relies on RV
                // being a compile-time constant.
                return i1 == i2;
            }
        };

        template<RuntimeValue RV>
        struct IsConvertibleTo
        {
            template<typename T>
            static bool go(int i)
            {
                // Pretend that this is some complicated code that relies on RV
                // being a compile-time constant.
                return static_cast<T>(i) == i;
            }
        };

        template<template<RuntimeValue> class FunctionWrapper, typename Function>
        struct Dispatcher
        {
            static Function * lookup(RuntimeValue rv)
            {
                switch (rv)
                {
                    case a: return &FunctionWrapper<a>::go;
                    case b: return &FunctionWrapper<b>::go;

                    default: assert(false); return 0;
                }
            }

            template<typename T>
            static Function * lookup(RuntimeValue rv)
            {
                switch (rv)
                {
                    case a: return &FunctionWrapper<a>::go<T>;
                    case b: return &FunctionWrapper<b>::go<T>;

                    default: assert(false); return 0;
                }
            }

            // And so on as needed...
            template<typename T1, typename T2>
            static Function * lookup(RuntimeValue rv);
        };

        RuntimeValue _rv;
};

int main()
{
    Big big(Big::a);

    assert(big.equals(3, 3));
    assert(big.isConvertibleTo<char>(123));
}
Run Code Online (Sandbox Code Playgroud)

这主要是有效的,除了:

  1. 它在Visual C++ 9(2008)下构建并正常工作,但在GCC 4.8下,它导致函数模板重载中的编译错误lookup().
  2. 它要求lookup()为我们想要支持的每个新的函数模板参数编写一个新的函数模板重载go().
  3. 使用起来很麻烦且令人困惑.

以下是GCC下发生的错误:

Big.cpp: In static member function 'static Function* Big::Dispatcher<FunctionWrapper, Function>::lookup(Big::RuntimeValue)':
Big.cpp(66,65) : error: expected primary-expression before '>' token
                         case a: return &FunctionWrapper<a>::go<T>;
                                                                 ^
Big.cpp(66,66) : error: expected primary-expression before ';' token
                         case a: return &FunctionWrapper<a>::go<T>;
                                                                  ^
Big.cpp(67,65) : error: expected primary-expression before '>' token
                         case b: return &FunctionWrapper<b>::go<T>;
                                                                 ^
Big.cpp(67,66) : error: expected primary-expression before ';' token
                         case b: return &FunctionWrapper<b>::go<T>;
                                                                  ^
Run Code Online (Sandbox Code Playgroud)

我的问题是双重的:

  1. 为什么这不能在GCC下构建,我该如何解决?
  2. 有没有更好的(即,不那么繁琐和令人困惑)的方式来做到这一点?

代码必须在Visual C++ 9(2008)下可编译,因此我不能使用C++ 11特定的任何东西.

And*_*owl 6

由于go是模板的从属名称,因此您需要使用template消除歧义器:

case a: return &FunctionWrapper<a>::template go<T>;
//                                  ^^^^^^^^
case b: return &FunctionWrapper<b>::template go<T>;
//                                  ^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

这告诉编译器解析::作为模板名称的作用域解析operator()后面的内容,以及后续的尖括号作为模板参数的分隔符.

为什么这不能在GCC下构建,我该如何解决?

因为GCC是符合标准,并进行两阶段的名称查找,而MSVC延迟名称查找,直到实例化时,因此,知道go是一个模板的名称.

在实例化之前,这些信息不可用,因为不可能知道什么T是,并且主模板可以专用于给定的,T因此go不是成员函数模板的名称,而是数据成员的名称.

这就是说,我希望MSVC template无论如何都支持消歧器,所以添加它应该使你的程序在GCC/Clang/what-c​​onforms-to-the-Standard和MSVC上编译.