如何转换/使用编译时表达式中的运行时变量

How to convert/use the run time variable in compile time expression?

本文关键字:表达式 编译 变量 运行时 何转换 转换      更新时间:2024-09-30

我有以下情况(实时代码: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()访问。