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

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 成员函数,如上面的最小示例中所述。正如上面代码中的注释所指出的,两者都无法工作,原因如下

  • 解决方案1:成员函数必须有唯一的返回类型
  • 解决方案 2:SFINAE 需要一个编译时表达式来决定选择哪个重载。

更新

std::variant如@lorro所述)将是这里最简单的解决方案。但是,需要 C++17 支持。

但是,我想知道我们是否有某种方法可以在编译器标志下工作?

注意:我正在使用一个代码库,其中无法使用 boost 等外部库,并且 CRTP 类结构大多是不可触及的。

Gug*_*ugi 4

由于您仅限于 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