2017-05-25 56 views
0

我想简化我在我的应用程序中编写的代码,该代码处理多种数据结构类型但使用通用标头。鉴于这样的事情:如何写一个模板来投射到基于整数/枚举的模板化类型

enum class MyType { 
    Foo = 100, 
    Bar = 200, 
}; 

struct Hdr { 
    MyType type; 
}; 

struct Foo { 
    Hdr hdr; 
    int x; 
    int y; 
    int z; 
}; 

struct Bar { 
    Hdr hdr; 
    double value; 
    double ratio; 
}; 

void process(const Foo *ptr) 
{ 
    // process Foo here 
} 

void process(const Bar *ptr) 
{ 
    // process Bar here 
} 

extern void *getData(); 

int main() 
{ 
    const void *pv = getData(); 
    auto pHdr = static_cast<const Hdr *>(pv); 
    switch (pHdr->type) { 
    case MyType::Foo: process(static_cast<const Foo *>(pv)); break; 
    case MyType::Bar: process(static_cast<const Bar *>(pv)); break; 
    default: throw "Unknown"; 
    } 
    return 0; 
} 

理想情况下,我想喜欢的东西来代替上述的switch语句:

process(multi_cast<pHdr->type>(pv); 

我具有写语句像这样得到它完全没问题工作:

template<MyType::Foo> 
const Foo *multi_cast(void *p) 
{ 
    return static_cast<const Foo *>(p); 
} 

template<MyType::Bar> 
const Bar *multi_cast(void *p) 
{ 
    return static_cast<const Bar *>(p); 
} 

但我不能写一个模板,其中模板参数是一个枚举(或与此有关的一个int) 有我只是看着这个这么久,我卡恩没有看到答案? 还是有没有其他的方式来做到这一点?

+3

不能使用运行时的值来选择模板特殊化。您将以某种方式需要在运行时检查该值,并决定调用哪个函数。 –

+0

为什么不使用虚拟功能? –

+0

您无法摆脱读循环中心的开关。这是因为您只知道在运行时将其转换为哪种类型。 – nakiya

回答

3

没有其他方法可以做到这一点。

正如评论指出的那样,由于类型在运行时存储在头文件中,因此您必须有的一些类型的运行时查找;由于所有这些都在编译期,所以没有多少模板或重载解析可以帮助您。

您可以尽可能多地抽象查找,但只能用另一种类型的查找来替换switch语句,并且只能从简单的开关/查找表中进一步减少性能。

例如,你可以开始像这样的东西,去坚果:

#include <iostream> 
#include <cassert> 

enum class Type { 
    FOO, 
    BAR, 
    NUM_ 
}; 

struct Header { 
    Header(Type t) 
     : type(t) 
    {} 

    Type type; 
}; 

struct Foo { 
    Foo(int x, int y, int z) 
     : header(Type::FOO), x(x), y(y), z(z) 
    {} 

    Header header; 
    int x; 
    int y; 
    int z; 
}; 

struct Bar { 
    Bar(double value, double ratio) 
     : header(Type::BAR), value(value), ratio(ratio) 
    {} 

    Header header; 
    double value; 
    double ratio; 
}; 

static inline void process(Foo*) { 
    printf("processing foo...\n"); 
} 

static inline void process(Bar*) { 
    printf("processing bar...\n"); 
} 

using ProcessFunc = void(*)(void*); 
static ProcessFunc typeProcessors[(size_t)Type::NUM_] = { 
    [](void* p) { process((Foo*)p); }, 
    [](void* p) { process((Bar*)p); }, 
}; 

static void process(void* p) { 
    Type t = ((Header*)p)->type; 
    assert((size_t)t < (size_t)Type::NUM_ && "Invalid Type."); 

    typeProcessors[(size_t)t](p); 
} 

static void* get_foo() 
{ 
    static Foo foo(0, 0, 0); 
    return &foo; 
} 

static void* get_bar() 
{ 
    static Bar bar(0.0, 0.0); 
    return &bar; 
} 

int main() { 
    Foo foo(0, 0, 0); 
    Bar bar(0.0, 0.0); 

    process(&foo); 
    process(&bar); 

    process(get_foo()); 
    process(get_bar()); 

    return 0; 
} 

但此时你只得到可爱,最有可能较慢。你可能只是把开关放在process(void*)

如果你没有序列化你的数据(可疑),一次大多处理一种类型,并且想要OO解决方案(我不会),你可以返回你的类型继承和基本类型添加纯虚函数process像这样:

struct Type { 
    virtual void process() = 0; 
    virtual ~Type() {} 
}; 

struct Foo : Type { 
    int x = 0; 
    int y = 0; 
    int z = 0; 

    virtual void process() override { 
     printf("processing foo...\n"); 
    } 
}; 

struct Bar : Type { 
    double value = 0.0; 
    double ratio = 0.0; 

    virtual void process() override { 
     printf("processing bar...\n"); 
    } 
}; 

static Type* get_foo() { 
    static Foo foo; 
    return &foo; 
} 

static Type* get_bar() { 
    static Bar bar; 
    return &bar; 
} 

int main() { 
    Foo foo; 
    Bar bar; 

    foo.process(); 
    bar.process(); 

    get_foo()->process(); 
    get_bar()->process(); 

    return 0; 
} 

我要坚持的开关,但我会继续类型:: foo的值,并键入:: BAR默认0和1.如果你把这些值弄得太多了,编译器可能会决定把这个开关看作一堆分支,而不是查找表。

-1

您正在使用可能被称为静态(=编译时)多态性的东西。这需要编制switch语句,以便将运行时间值pHrd->dtype转换为case子句中编译时值处理之一。喜欢你的

process(multi_cast<pHdr->type>(pv); 

事情是不可能的,因为pHdr->type在编译时已知的。

如果你想避免switch,你可以使用普通的动态多态性忘记有关enum Hdr,但使用一个抽象基类

struct Base { 
    virtual void process()=0; 
    virtual ~Base() {} 
}; 

struct Foo : Base { /* ... */ }; 
struct Bar : Base { /* ... */ }; 

Base*ptr = getData(); 
ptr->process(); 
+1

你忘了让基础析构函数为虚拟吗? – rationalcoder

+0

@rationalcodet确实。发现得好! – Walter

+0

您错过了IMO最关键的OP问题:有一个枚举值,它将确定要创建的对象的类型(并因此扩展行为)。在你的例子中,它隐藏在'getData'后面。 –

-1

你有两个问题:

  1. 转换运行时的值(你的“类型”)成编译时确定型(带相关联的行为)。
  2. “统一”的可能的不同类型,以单一的(静态已知在编译时)型。

第2点就是继承与virtual成员函数一起为:

struct Thing { 
    virtual void doStuff() const = 0; 
    virtual ~Thing() {} 
}; 
struct AThing : Thing { 
    void doStuff() const override { std::cout << "A"; } 
}; 
struct BThing : Thing { 
    void doStuff() const override { std::cout << "B"; } 
}; 

1点通常是通过建立某种形式的“工厂”的机制,然后调度;基于运行时的价值解决其中一家工厂。首先,工厂:

Thing * factoryA() { return new AThing(); } 
Thing * factoryB() { return new BThing(); } 
Thing * factory_failure() { throw 42; } 

的“调度”(或“选择合适的工厂”),可以以不同的方式来完成,这些是你的switch声明的一个(快,但很笨拙),线性搜索通过一些容器/数组(容易,缓慢)或通过在地图中查找(对数 - 或基于哈希的地图的常量)。

我选择了(有序)的地图,但不是使用std::map(或std::unordered_map):我用一个std::array避免动态内存分配(排序!):

// Our "map" is nothing more but an array of key value pairs. 
template < 
    typename Key, 
    typename Value, 
    std::size_t Size> 
using cmap = std::array<std::pair<Key,Value>, Size>; 


// Long type names make code hard to read. 
template < 
    typename First, 
    typename... Rest> 
using cmap_from = 
    cmap<typename First::first_type, 
     typename First::second_type, 
     sizeof...(Rest) + 1u>; 


// Helper function to avoid us having to specify the size 
template < 
    typename First, 
    typename... Rest> 
cmap_from<First, Rest...> make_cmap(First && first, 
            Rest && ... rest) { 
    return {std::forward<First>(first), std::forward<Rest>(rest)...}; 
} 

使用std::lower_bound我上执行二进制搜索此有序阵列(EHM“地图”):

// Binary search for lower bound, check for equality 
template < 
    typename Key, 
    typename Value, 
    std::size_t Size> 
Value get_from(cmap<Key,Value,Size> const & map, 
       Key const & key, 
       Value alternative) { 
    assert(std::is_sorted(std::begin(map), std::end(map), 
         [](auto const & lhs, auto const & rhs) { 
          return lhs.first < rhs.first; })); 
    auto const lower = std::lower_bound(std::begin(map), std::end(map), 
             key, 
             [](auto const & pair, auto k) { 
             return pair.first < k; }); 
    if (lower->first == key) { 
    return lower->second; 
    } else { 
    // could also throw or whatever other failure mode 
    return alternative; 
    } 
} 

这样,终于,我可以用一个static const地图获得工厂给予一定的运行时间值“类型”(或选择,因为我把它命名):

int main() { 
    int const choices[] = {1, 10, 100}; 
    static auto const map = 
    make_cmap(std::make_pair(1, factoryA), 
       std::make_pair(10, factoryB)); 
    try { 
    for (int choice : choices) { 
     std::cout << "Processing choice " << choice << ": "; 
     auto const factory = get_from(map, choice, factory_failure); 
     Thing * thing = factory(); 
     thing->doStuff(); 
     std::cout << std::endl; 
     delete thing; 
    } 
    } catch (int const & value) { 
    std::cout << "Caught a " << value 
       << " ... wow this is evil!" << std::endl; 
    } 
} 

(Live on ideone)

的是 “地图” 大概可以做constexpr初始化。

当然,不是原始指针(Thing *),你应该使用托管指针(如std::unique_ptr)。此外,如果你不想让你的处理(doStuff)的成员函数,那么就做一个单一的“调度”(virtual)成员调用了给定函数对象,传递自己的实例(this)功能。使用CRTP基类,您不需要为每个类型实现该成员函数。

+1

如果最后你通过基类指针'thing'做了一个'virtual'函数调用,那么所有这个map的意义何在?我想你想要一个更好的例子。 – Walter

+1

@Walter没有一点。这是现代C++疾病的教科书范例。这个例子很慢,不必要的复杂,因此不可读。不惜一切代价避免这样的解决方案。 – rationalcoder

+0

@Walter映射将运行时枚举值映射到创建具有不同(动态)类型的对象。这就是问题所要求的。如果对象是“一次性使用”并且不模拟一些更复杂的行为,则虚拟成员函数可以省略。 –