【问题标题】:How to convert/use the run time variable in compile time expression?如何在编译时表达式中转换/使用运行时变量?
【发布时间】:2022-08-21 17:38:32
【问题描述】:

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

问题是子类选择(必须调用doSomething_Impl())应该通过确定SomeLogicClass 的运行时变量mClassId 来进行。

我能想到的唯一两种可能的解决方案是普通的 switch case 和 SFINAE 的成员函数,如上面的最小示例中所述。如上面代码中的 cmets 所述,两者都无法工作,原因是

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

更新

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

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

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

  • 也许我错过了一些明显的东西,但是使用 (void *) 呢?

标签: c++11 c++ c++11 templates runtime compile-time


【解决方案1】:

目前尚不清楚您是否想要一个元素或每个元素之一。

如果您需要其中一个,SomeLogicClass 应该有一个std::tuple&lt;Class1, Class2, Class3&gt;。这存储了“一个”,还提供了std::get&lt;&gt;(),它返回元素i。因此,getInstance() 基本上可以委托给std::get&lt;ID&gt;()

如果您需要任一类型的元素,SomeLogicClass 应该有一个std::variant&lt;Class1, Class2, Class3&gt;。这存储了活动元素的种类和元素本身。您可以使用std::get&lt;&gt;(),或者-更好-您可以通过std::visit()访问。

【讨论】:

  • SomeLogicClass::getInstance() 将被调用一次只访问一个元素或类实例(即mChild1mChild2mChild3)。然而,在整个执行过程中,可能会调用SomeLogicClass::setId() 来选择/设置任何类实例。简而言之,std::get&lt;ID&gt;() 中的 ID 在这种情况下将是运行时变量。
  • @JeJo 如果您只有一个活动元素(并且您可以破坏非活动元素),那么它本质上是std::variant&lt;&gt;。建议直接使用。
  • 如果可以的话,我肯定会;因为我仅限于 C++11 :(。无论如何感谢您的建议。
  • @JeJo如果是c++11,那么你有boost::variant&lt;&gt;,它“基本相同”(阅读:移植时有许多细微差别,但可能有效)。
【解决方案2】:

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

请注意新函数 apply() 如何替换您的 getInstance()。但它不是尝试返回子类型,而是接受一些适用于正确子类型的通用操作。传入的函子需要处理(即编译)所有可能的子类型。由于它们都有doSomething() 方法,您可以简单地使用模板仿函数(DoSomethingCaller)。不幸的是,在 C++14 之前,它不能简单地是一个多态 lambda,而是需要在函数之外是一个适当的结构 (DoSomethingCaller)。

如果您愿意,您还可以将 DoSomethingCaller 限制为 CRTP 基类 Base&lt;T&gt;

struct DoSomethingCaller
{
    template <class T>
    void operator()(Base<T> & childInstance){
        childInstance.doSomething();
    }
};

这可能使它更具可读性。

根据“无外部库”限制的严格程度,可能只允许 boost,但单个外部头文件(可以像任何其他头文件一样简单地包含在代码库中)是可能的?如果是,您可能还想看看variant-lite。它旨在成为std::variant 的兼容 C++98/C++11 的替代品。

【讨论】:

  • 我非常喜欢这种方法。我将在实际用例中检查这一点。
【解决方案3】:

我在这里认识到您的 CRTP 代码库中的桥接模式,尽管在我看来做得很糟糕。但是无论如何,如果您无法触及该代码,那么您应该实现第二个桥接代码。幸运的是,有了模板,这并不难。

这是代码(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 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 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);
}

我在这里和那里省略了错误检查,因此代码更具可读性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-13
    相关资源
    最近更新 更多