重新解释没有类型标识符的 cast void
Reinterpret cast void without type identifier
我正在使用类型擦除(void cast)在一对一映射中存储多个基本类型(float、bool、int 等)。重新解释原始数据类型的一种方法是使用 pair/union/class 来存储带有类型标识符(例如 pair)的值。有没有一种干净的方法可以在没有 Boost 的情况下使用 C++11 解析没有标识符的基本类型?
std::map<int, void *> m_mymap;
// avoid std::map<int, pair<void, MyEnum> > m_mymap;
template <class T> void setValue<T>(int i_key, T i_val)
{
m_mymap[i_key] = reinterprete_cast<void *>(i_val);
}
template <class T> T getValue<T>(int i_key)
{
return reinterprete_cast<T>(i_val);
}
doSomeWork()
{
for (const auto & elem : m_mymap)
{
auto val = some_magical_cast< ??? >(elem.second) // resolve type without a nasty switch against MyEnum
// do some work
}
}
编辑:更新问题以使用 void *
auto val = some_magical_cast< ??? >(elem.second) // resolve type without a nasty switch against MyEnum
C++ 是一种静态 类型的语言。必须在编译时知道每种类型。您要执行的操作需要根据放入地图的内容在 运行时 确定类型。
这在 C++ 中是不可能的。 val
必须具有单一的特定类型,而不是通过执行运行时代码确定的类型。并且循环的每次迭代都必须给它相同的类型。联合或 variant
将允许您存储在运行时确定的几种类型之一。但是它们存储的类型集在编译时是固定的。
即使 auto val
可能以某种方式产生一系列不同的类型,void*
也不包含任何可用于恢复其指向的类型的信息。请注意 boost/std::any
也没有任何恢复此类型的能力;你必须要求一个特定的类型(区别在于如果你要求错误的类型,any
将失败,而 void*
会给你 UB)。
所以不,这是不可能的。
正如@NicolBolas 正确解释的那样,你不能。
作为可行的approach/workaround,您可以使用基于双重调度思想的技术,如下例所示:
#include<map>
#include<utility>
#include<iostream>
class C {
public:
template<typename T>
void accept(int k, T v) { std::cout << k << " " << v << std::endl; }
};
using P = void(*)(C &, int, void *);
std::map<int, std::pair<P, void*>> m;
template<typename T>
void proto(C &c, int k, void *v) {
c.accept(k, *static_cast<T*>(v));
}
template <class T>
void set(int k, T *v) {
m[k] = { &proto<T>, v };
}
void get(C &c, int k) {
auto p = m[k];
p.first(c, k, p.second);
}
int main() {
int i = 42;
double d = 1.2;
set(0, &i);
set(1, &d);
C c;
for(auto &&k: m) {
get(c, k.first);
}
}
它需要额外的 class(示例中的 C
)。
您可以为不同的类型添加 class 不同的方法(例如,您可以使用两种接受 int
或 double
的方法来代替模板方法)。
这样,您既可以拥有通用行为,也可以拥有许多特定类型的行为。
当然,它更复杂,而且不是免费的,否则你无法得到你想要的。
我正在使用类型擦除(void cast)在一对一映射中存储多个基本类型(float、bool、int 等)。重新解释原始数据类型的一种方法是使用 pair/union/class 来存储带有类型标识符(例如 pair)的值。有没有一种干净的方法可以在没有 Boost 的情况下使用 C++11 解析没有标识符的基本类型?
std::map<int, void *> m_mymap;
// avoid std::map<int, pair<void, MyEnum> > m_mymap;
template <class T> void setValue<T>(int i_key, T i_val)
{
m_mymap[i_key] = reinterprete_cast<void *>(i_val);
}
template <class T> T getValue<T>(int i_key)
{
return reinterprete_cast<T>(i_val);
}
doSomeWork()
{
for (const auto & elem : m_mymap)
{
auto val = some_magical_cast< ??? >(elem.second) // resolve type without a nasty switch against MyEnum
// do some work
}
}
编辑:更新问题以使用 void *
auto val = some_magical_cast< ??? >(elem.second) // resolve type without a nasty switch against MyEnum
C++ 是一种静态 类型的语言。必须在编译时知道每种类型。您要执行的操作需要根据放入地图的内容在 运行时 确定类型。
这在 C++ 中是不可能的。 val
必须具有单一的特定类型,而不是通过执行运行时代码确定的类型。并且循环的每次迭代都必须给它相同的类型。联合或 variant
将允许您存储在运行时确定的几种类型之一。但是它们存储的类型集在编译时是固定的。
即使 auto val
可能以某种方式产生一系列不同的类型,void*
也不包含任何可用于恢复其指向的类型的信息。请注意 boost/std::any
也没有任何恢复此类型的能力;你必须要求一个特定的类型(区别在于如果你要求错误的类型,any
将失败,而 void*
会给你 UB)。
所以不,这是不可能的。
正如@NicolBolas 正确解释的那样,你不能。
作为可行的approach/workaround,您可以使用基于双重调度思想的技术,如下例所示:
#include<map>
#include<utility>
#include<iostream>
class C {
public:
template<typename T>
void accept(int k, T v) { std::cout << k << " " << v << std::endl; }
};
using P = void(*)(C &, int, void *);
std::map<int, std::pair<P, void*>> m;
template<typename T>
void proto(C &c, int k, void *v) {
c.accept(k, *static_cast<T*>(v));
}
template <class T>
void set(int k, T *v) {
m[k] = { &proto<T>, v };
}
void get(C &c, int k) {
auto p = m[k];
p.first(c, k, p.second);
}
int main() {
int i = 42;
double d = 1.2;
set(0, &i);
set(1, &d);
C c;
for(auto &&k: m) {
get(c, k.first);
}
}
它需要额外的 class(示例中的 C
)。
您可以为不同的类型添加 class 不同的方法(例如,您可以使用两种接受 int
或 double
的方法来代替模板方法)。
这样,您既可以拥有通用行为,也可以拥有许多特定类型的行为。
当然,它更复杂,而且不是免费的,否则你无法得到你想要的。