C++ - 在初始化成员类的条件下启用成员函数

C++ - enable usage of member functions under condition that member class was intialize

提问人:linuxbeginner 提问时间:10/18/2023 最后编辑:linuxbeginner 更新时间:10/18/2023 访问量:92

问:

假设我有一个不包含任何成员并执行计算的类:

class Helper{
public: 
    bool f1(TypeA a, TypeB b, Result& res){
        // Some calculation requires TypeA, and TypeB and are assigned to the result
        return true;
    }
    
    bool f2(TypeC c, Result& res){
        // Some calculation requires TypeC and assign to the result
        return true;
    }
    
    bool f3(TypeA a, TypeC c, Result& res){
        // Some calculation requires TypeC and assign to the result
        return true;
    }
    
    bool f4(Result& res){
        // Some calculation that doesn't require anything and is assigned to the result
        return true;
    }

};

其中 TypeA、TypeB 和 TypeC 是一些类型。

只有当我有这些类型时,我才想使用辅助类,有些函数可能不需要任何成员类,有些只需要TypeC,有些需要两种类型等,所以我有以下类:

class Item{

public: 

// Constructors, may or may not initialize some values
Item(){} 
Item(TypeA a, TypeB b, TypeC c):a_(a),b_(b),c_(c){}
Item(TypeB b, TypeC c):b_(b),c_(c){}


// usage of member functions 
bool f1(Result& res){
    if (a_.has_value() && b_.has_value())
    {
        return h.f1(a_.value(),b_.value(),res);
    } 
    // runtime error 
    throw std::logic_error("some msg");
}

bool f2(Result& res){
    // Allow only if c was initiated
    if (c_.has_value())
    {
        return h.f2(c_.value(), res);
    } 
    // runtime error 
    throw std::logic_error("some msg");
}

bool f4(Result& res){
        // Doesn't require any member class
        return h.f4(res);
}

private:

std::optional<TypeA> a_;
std::optional<TypeB> b_;
std::optional<TypeC> c_;
Helper h;

};

因此,只有当用户正确使用它并启动所需的成员时,他们才能期望具有 Helper 功能。但这是一个运行时错误,而不是编译时错误。所以我想也许只有在满足条件的情况下我才能允许编译。

我正在研究这个线程:c++ 启用、禁用类成员函数?

但这可能会导致代码重复(因为我需要将每个函数复制到这些类中),并且不可维护,因为对于每个添加的函数,我需要问自己“哪些类可以使用此函数?那么也许有更好的解决方案?我很想听听你的想法。

c++ oop 运行时错误 编译时

评论

3赞 Ted Lyngmo 10/18/2023
Helper看起来它应该是一个而不是一个.namespaceclass
0赞 linuxbeginner 10/18/2023
如果我没有使用覆盖和同名函数,那将是真的(我没有在这里添加它,因为它会使问题膨胀)
0赞 Ted Lyngmo 10/18/2023
那么,它们是函数吗?谁继承了班级?似乎缺少一些拼图virtual
1赞 Ted Lyngmo 10/18/2023
好的,但你也可以做一个重载。namespace
2赞 Miles Budnek 10/18/2023
我认为如果不拆分为多种类型,您想要的是不可能的。考虑。这应该会导致错误吗?在执行运行时之前无法知道,这对于编译时错误来说为时已晚。ItemItem i{}; if (rand() % 2 == 0) i = Item{a, b, c}; f1(res);rand()

答:

0赞 Cyber fahad 10/18/2023 #1

添加一个额外的模板参数 T,其默认值为要检查的类型(TypeA 或 TypeC) 使用 std::enable_if 和 std::is_same 仅在 T 与所需类型匹配时启用该函数 现在,仅当 TypeA 存在时才启用 f1,仅当 TypeC 存在时才启用 f2,依此类推。

    #include <type_traits>
#include <optional>

class Helper {
  // Same as before 
};

class Item {
public:

  template <typename T = TypeA, 
            typename = std::enable_if_t<std::is_same_v<T, TypeA>>>
  bool f1(TypeA a, TypeB b, Result& res) {
    return h.f1(a, b, res); 
  }

  template <typename T = TypeC,
            typename = std::enable_if_t<std::is_same_v<T, TypeC>>>  
  bool f2(TypeC c, Result& res) {
    return h.f2(c, res);
  }

  bool f4(Result& res) {
    return h.f4(res);
  }

private:
  std::optional<TypeA> a_;
  std::optional<TypeB> b_;
  std::optional<TypeC> c_;
  
  Helper h;
};

评论

0赞 linuxbeginner 10/18/2023
对不起,我的函数符号有误,所以我只有 f1,f2,f3,f4,但它将使用帮助程序并且只接受参数 Result(因为它确实具有他的成员函数中需要的东西)。
2赞 Ted Lyngmo 10/18/2023 #2

我建议在使用这三个初始化时不要使用相同的类型,并且不要使用所有三个初始化时相同的类型 - 或者仅使用.它们不共享公共接口,也不携带相同的数据。我会做一个班级模板。ItemTypeATypeBTypeCItemItem

在这里,我使用 a 作为私有基类来存储所提供类型的值。std::tuple

例:

#include <tuple>
#include <type_traits>

// Helper trait to check if a type is any of the supplied types
template <class T, class... Ts>
inline constexpr bool is_any_of_v = std::disjunction_v<std::is_same<T, Ts>...>;

template <class... Ts>
class Item : private std::tuple<Ts...> {
public:
    using std::tuple<Ts...>::tuple;
    using std::tuple<Ts...>::operator=;

    template<class A = TypeA, class B = TypeB> 
    std::enable_if_t<is_any_of_v<A, Ts...> && is_any_of_v<B, Ts...>, bool>
    f1(Result& res) {
        return Helper::f1(std::get<TypeA>(*this), std::get<TypeB>(*this), res);
    }

    template<class C = TypeC>
    std::enable_if_t<is_any_of_v<C, Ts...>, bool>
    f2(Result& res) {
        return Helper::f2(std::get<TypeC>(*this), res);
    }

    bool f4(Result& res) {
        // Doesn't require any member class
        return Helper::f4(res);
    }
};
template <class... Ts>
Item(Ts...) -> Item<std::remove_cvref_t<Ts>...>;

注意:我在这里将您的函数放在 .Helpernamespace

然后可以这样使用它:

int main() {
    TypeA a;
    TypeB b;
    Result r;
    Item item(a, b);
    item.f1(r);
    // item.f2(r); // compilation error, has no TypeC so f2() doesn't exist
}

item在上面的例子中,be 的类型与 an 或 etc 没有任何关系。您供应的每个独特的类型组合都将产生一个唯一的类型。Item<TypeA, TypeB>Item<TypeC>Item<TypeB, TypeA>Item

演示

使用 C++20,您可以将 s 替换为子句:
演示由 Miles Budnek 友情提供
std::enable_if_trequires

评论

1赞 Miles Budnek 10/18/2023
也许值得一提的是,这些成员函数模板可以转换为具有 C++20: godbolt.org/z/eKoaWd7b4 的子句requires
0赞 Ted Lyngmo 10/18/2023
@MilesBudnek确实如此!谢谢!