JeJ*_*eJo 7 c++ templates runtime compile-time c++11
我有以下情况(实时代码: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 implementation\n"; }
};
struct Child2 : public Base<Child2> {
void doSomething_Impl() { std::cout << "Child2 implementation\n"; }
};
struct Child3 : public Base<Child3> {
void doSomething_Impl() { std::cout << "Child3 implementation\n"; }
};
// ... 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);
}
Run Code Online (Sandbox Code Playgroud)
问题是子类选择(必须调用 )应该通过决定 的运行时变量doSomething_Impl()来进行。mClassIdSomeLogicClass
我能想到的唯一两种可能的解决方案是正常的 switch 情况和 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 implementation\n"; }
};
struct Child2 : public Base<Child2> {
void doSomething_Impl() { std::cout << "Child2 implementation\n"; }
};
struct Child3 : public Base<Child3> {
void doSomething_Impl() { std::cout << "Child3 implementation\n"; }
};
// ... 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);
}
Run Code Online (Sandbox Code Playgroud)
请注意新函数如何apply()替换您的getInstance(). 但它不是尝试返回子类型,而是接受一些应用于正确子类型的通用操作。传入的仿函数需要处理(即编译)所有可能的子类型。由于它们都有一个doSomething()方法,因此您可以简单地使用模板化函子 ( DoSomethingCaller)。不幸的是,在 C++14 之前,它不能简单地是一个多态 lambda,而需要是DoSomethingCaller函数外部的一个适当的 struct ()。
如果您愿意这样做,您还可以限制DoSomethingCaller为 CRTP 基类Base<T>:
struct DoSomethingCaller
{
template <class T>
void operator()(Base<T> & childInstance){
childInstance.doSomething();
}
};
Run Code Online (Sandbox Code Playgroud)
这可能会使其更具可读性。
根据“无外部库”限制的严格程度,也许只不允许 boost,但可以使用单个外部头文件(可以像任何其他头文件一样简单地包含在代码库中)?如果是,您可能还想看看variant-lite。它的目标是成为 C++98/C++11 兼容的替代品std::variant。