如何转换/使用编译时表达式中的运行时变量
How to convert/use the run time variable in compile time expression?
我有以下情况(实时代码:https://gcc.godbolt.org/z/d8jG9bs9a):
#include <iostream>
#include <type_traits>
#define ENBALE true // to enable disable test solutions
enum struct Type : unsigned { base = 0, child1, child2, child3 /* so on*/ };
// CRTP Base
template<typename Child> struct Base {
void doSomething() { static_cast<Child*>(this)->doSomething_Impl(); }
private:
Base() = default;
friend Child;
};
struct Child1 : public Base<Child1> {
void doSomething_Impl() { std::cout << "Child1 implementationn"; }
};
struct Child2 : public Base<Child2> {
void doSomething_Impl() { std::cout << "Child2 implementationn"; }
};
struct Child3 : public Base<Child3> {
void doSomething_Impl() { std::cout << "Child3 implementationn"; }
};
// ... so on
class SomeLogicClass
{
Type mClassId{ Type::base };
Child1 mChild1;
Child2 mChild2;
Child3 mChild3;
public:
Type getId() const { return mClassId; }
void setId(Type id) { mClassId = id; } // run time depended!
#if ENBALE // Solution 1 : simple case
/*what in C++11?*/ getInstance()
{
switch (mClassId)
{
case Type::child1: return mChild1;
case Type::child2: return mChild2;
case Type::child3: return mChild3;
default: // error case!
break;
}
}
#elif !ENBALE // Solution 2 : SFINAE
template<Type ID>
auto getInstance() -> typename std::enable_if<ID == Type::child1, Child1&>::type { return mChild1; }
template<Type ID>
auto getInstance() -> typename std::enable_if<ID == Type::child2, Child2&>::type { return mChild2; }
template<Type ID>
auto getInstance() -> typename std::enable_if<ID == Type::child3, Child3&>::type { return mChild3; }
#endif
};
void test(SomeLogicClass& ob, Type id)
{
ob.setId(id);
#if ENBALE // Solution 1
auto& childInstance = ob.getInstance();
#elif !ENBALE // Solution 2
auto& childInstance = ob.getInstance<ob.getId()>();
#endif
childInstance.doSomething(); // calls the corresponding implementations!
}
int main()
{
SomeLogicClass ob;
test(ob, Type::child1);
test(ob, Type::child2);
test(ob, Type::child3);
}
问题是,应该通过决定SomeLogicClass
的运行时变量mClassId
来进行子类选择(必须调用doSomething_Impl()
(。
我能想到的唯一两种可能的解决方案是正常的开关情况和SFINAE成员函数,如上面的最小示例中所述。正如上面代码中的注释所指出的,由于的原因,两者都无法工作
- 解决方案1:成员函数必须具有唯一的返回类型
- 解决方案2:SFINAE需要一个编译时表达式来决定要选择哪个重载
更新
std::variant
(正如@lorro所提到的(将是这里最简单的解决方案。但是,需要C++17支持。
但是,我想知道我们是否有办法绕过编译器标志c++11下的哪个工作
注意:我使用的是代码库,其中不能使用诸如boost之类的外部库,并且CRTP类结构大多是不可访问的。
由于您被限制为C++11,并且不允许使用boost::variant
等外部库,因此另一种选择是颠倒逻辑:不要试图返回子类型,而是传入要在子类型上执行的操作。你的例子可能会变成这样(godbolt(:
#include <iostream>
#include <type_traits>
enum struct Type : unsigned { base = 0, child1, child2, child3 /* so on*/ };
// CRTP Base
template<typename Child> struct Base {
void doSomething() { static_cast<Child*>(this)->doSomething_Impl(); }
private:
Base() = default;
friend Child;
};
struct Child1 : public Base<Child1> {
void doSomething_Impl() { std::cout << "Child1 implementationn"; }
};
struct Child2 : public Base<Child2> {
void doSomething_Impl() { std::cout << "Child2 implementationn"; }
};
struct Child3 : public Base<Child3> {
void doSomething_Impl() { std::cout << "Child3 implementationn"; }
};
// ... so on
class SomeLogicClass
{
Type mClassId{ Type::base };
Child1 mChild1;
Child2 mChild2;
Child3 mChild3;
// ... child3 so on!
public:
Type getId() const { return mClassId; }
void setId(Type id) { mClassId = id; } // run time depended!
template <class Func>
void apply(Func func)
{
switch (mClassId){
case Type::child1: func(mChild1); break;
case Type::child2: func(mChild2); break;
case Type::child3: func(mChild3); break;
default: // error case!
break;
}
}
};
struct DoSomethingCaller
{
template <class T>
void operator()(T & childInstance){
childInstance.doSomething();
}
};
void test(SomeLogicClass& ob, Type id)
{
ob.setId(id);
ob.apply(DoSomethingCaller{});
// Starting with C++14, you can also simply write:
//ob.apply([](auto & childInstance){ childInstance.doSomething(); });
}
int main()
{
SomeLogicClass ob;
test(ob, Type::child1);
test(ob, Type::child2);
test(ob, Type::child3);
}
请注意新函数apply()
是如何替换getInstance()
的。但是,它不尝试返回子类型,而是接受一些应用于正确子类型的泛型操作。传入的函子需要处理(即编译(所有可能的子类型。由于它们都有一个doSomething()
方法,所以您可以简单地使用一个模板化函子(DoSomethingCaller
(。不幸的是,在C++14之前,它不能简单地是多态lambda,而需要是函数之外的适当结构(DoSomethingCaller
(。
如果您愿意这样做,还可以将DoSomethingCaller
限制为CRTP基类Base<T>
:
struct DoSomethingCaller
{
template <class T>
void operator()(Base<T> & childInstance){
childInstance.doSomething();
}
};
这可能会使其可读性更强。
取决于";没有外部库";限制是,也许只有boost是不允许的,但单个外部头(可以像任何其他头文件一样简单地包含在代码库中(是可能的?如果是,您可能还想看看变体lite。它的目标是成为一个兼容C++98/C++11的std::variant
的替代品。
我在这里认出了CRTP代码库中的Bridge模式,尽管在我看来做得很糟糕。但无论如何,如果您不能接触到那个代码,那么您应该实现第二个桥接代码。幸运的是,有了模板,这不会那么难。
这是代码(godbold(:
#include <iostream>
enum struct Type : unsigned { base = 0, child1, child2, child3 /* so on*/ };
// CRTP Base
template<typename Child> struct Base {
void doSomething() { static_cast<Child*>(this)->doSomething_Impl(); }
private:
Base() = default;
friend Child;
};
struct Child1 : public Base<Child1> {
void doSomething_Impl() { std::cout << "Child1 implementationn"; }
};
struct Child2 : public Base<Child2> {
void doSomething_Impl() { std::cout << "Child2 implementationn"; }
};
struct Child3 : public Base<Child3> {
void doSomething_Impl() { std::cout << "Child3 implementationn"; }
};
// ... so on
class CommonInterface {
public:
virtual void doSomething() = 0;
};
template<class Child>
class ChildCaller : public CommonInterface {
private:
Child child;
public:
virtual void doSomething() {
child.doSomething_Impl();
}
};
class SomeLogicClass
{
Type mClassId{ Type::base };
ChildCaller<Child1> mChild1;
ChildCaller<Child2> mChild2;
ChildCaller<Child3> mChild3;
CommonInterface* currentChild;
public:
Type getId() const { return mClassId; }
void setId(Type id) {
mClassId = id;
switch(id) {
case Type::child1:
currentChild = &mChild1;
break;
case Type::child2:
currentChild = &mChild2;
break;
case Type::child3:
currentChild = &mChild3;
break;
default:
currentChild = nullptr; // or anything you want
}
}
void doSomething() {
currentChild->doSomething();
}
};
void test(SomeLogicClass& ob, Type id)
{
ob.setId(id);
ob.doSomething(); // calls the corresponding implementations!
}
int main() {
SomeLogicClass ob;
test(ob, Type::child1);
test(ob, Type::child2);
test(ob, Type::child3);
}
我省略了这里和那里的错误检查,所以代码更可读。
不清楚您想要一个元素还是每个元素中的一个。
如果您需要其中的一个,SomeLogicClass应该有一个std::tuple<Class1, Class2, Class3>
。这存储"每个一个",还提供std::get<>()
,返回元素i
。因此,getInstance()
基本上可以委托给std::get<ID>()
。
如果您需要任何一种类型的元素,SomeLogicClass都应该有一个std::variant<Class1, Class2, Class3>
。这存储了活动元素的种类和元素本身。您可以使用std::get<>()
,也可以通过std::visit()
访问。
- 在VS2010-VS2015下编译时,如何使用decltype作为较大类型表达式的LHS
- 如何确认我的constexpr表达式实际上已经在编译时执行
- 断言中的Fold表达式在某些计算机上编译,但在其他计算机上不编译
- 即使使用调试编译标志,表达式也是"optimized out"
- 编译 llvm 3.1 时,为什么会出现错误:在">"标记之前预期主表达式
- C++ 编译错误:意外的类型名称"字符串":预期的表达式
- 如何在常量计算表达式中获取编译时错误?
- 不是 VS2017 中的编译时常量表达式
- C++编译错误(有符号和无符号整数表达式之间的比较)
- 将编译时定义大小的数组初始化为常量表达式
- 用MASM编译汇编文件时,缺少表达式操作员
- 如何判断表达式是在编译时还是运行时计算的?
- 基于用户表达式在编译时参数化函数
- 是否可以表示不应编译的表达式的静态_assert
- 使用折叠表达式初始化静态 constexpr 类数据成员不编译
- 当使用无效表达式时,概念是否应该无法编译?
- 减少 std::正则表达式编译时间 C++
- 如何修复 pimpl 实现中预期的主要表达式编译错误
- C++预期的主表达式编译错误
- 由于抽象模板arg的实例化,Boost::lambda表达式编译失败.任何解释和/或解决方法