【问题标题】:C++ interface without inheritance for template functions没有模板函数继承的 C++ 接口
【发布时间】:2020-03-03 17:29:34
【问题描述】:

有两个不同的类具有交叉的方法集:

class A {
public:
    int Value1() { return 100; }
    char Value2() { return "a"; }
    void actionA() { };
}

class B {
public:
    int Value1() { return 200; }
    char Value2() { return "b"; }
    void actionB() { };
}

类接口的公共部分可以这样描述:

class GenericPart {
public:
  virtual int Value1();
  virtual char Value2();
}

请注意,AB 类来自某个库,因此不能从 GenericPart 继承。

有一个模板函数可用于实现GenericPart 中描述的方法的对象:

template <typename T>
void function(T record) {
    std::cout << record.Value1() << " " << record.Value2() << std::endl;
}

是否可以专门化此模板函数以使其仅接收与GenericPart 匹配的对象?

【问题讨论】:

  • 当您说 是否可以专门化此模板函数以使其仅接收与 GenericPart 匹配的对象? 这是否意味着您不希望或确实希望它接受 @ 987654329@ 和 B 也一样?
  • 改用指针(无论如何都需要多态性工作),并创建一个采用GenericPart* 参数的overload
  • @Someprogrammerdude 我建议引用更安全,不必检查 nullptr。
  • @Someprogrammerdude 似乎这完全不可能,因为 “类 A 和 B 来自某个库,因此不能从 GenericPart 继承。”
  • 匹配Genericpart是什么意思? AB 是在上面做的吗?

标签: c++ templates interface template-specialization


【解决方案1】:

您可以使用 C++20 功能:concepts & constraints,但更简单的解决方案可能涉及static_assert。解释见function中的cmets

#include <type_traits>
#include <iostream>

class A {
public:
    int Value1() { return 100; }
    char Value2() { return 'a'; }
    void actionA() { };
};

class B {
public:
    int Value1() { return 200; }
    char Value2() { return 'b'; }
    void actionB() { };
};

class C  { // Has the required functions but with different return types
public:
    double Value1() { return 0.0; }
    double Value2() { return 1.0; }
};

class D  { // Has only one required function
public:
    int Value1() { return 300; }
};


template <typename T>
void function(T record) {
    // Check statically that T contains a `Value1()` that returns an int
    static_assert(std::is_same_v<int, decltype(record.Value1())>, "int Value1() required!");

    // Check statically that T contains a `Value2()` that returns an char
    static_assert(std::is_same_v<char, decltype(record.Value2())>, "char Value2() required!");

    std::cout << record.Value1() << " " << record.Value2() << std::endl;
}

int main()
{
    A a;
    B b;
    C c;
    D d;

    function(a); // Ok
    function(b); // Ok
    function(c); // causes static_assert to fail
    function(d); // causes static_assert to fail
}

【讨论】:

    【解决方案2】:

    是否可以专门化此模板函数以使其仅接收与 GenericPart 匹配的对象?

    您不能部分特化模板函数。

    但是SFINAE可以根据T的特点f开启/关闭不同的版本。

    例如,您可以只为支持Value1()Value2() 方法的T 启用function(),如下所示

    template <typename T>
    auto function (T record)
       -> decltype( record.Value1(), record.Value2(), void() )
     { std::cout << record.Value1() << " " << record.Value2() << std::endl; }
    

    如果不支持某些方法,禁用 function() 会更复杂(但请参阅 Timo 的回答:给定 has_feature_set 变得微不足道)所以我建议添加某种类型的未使用参数(例如,int )

    template <typename T> // VVV  <-- unused argument
    auto function (T record, int)
       -> decltype( record.Value1(), record.Value2(), void() )
     { std::cout << record.Value1() << " " << record.Value2() << std::endl; }
    

    并开发一个通用函数,该函数接受另一种(但兼容的)类型(例如long)的未使用类型并一直启用

    template <typename T> // VVVV  <-- unused argument
    void function (T record, long)
     { /* do something with record */ }
    

    现在你可以写一个一级function()如下

    template <typename T>
    void function (T record)
     { function(record, 0); }
    

    观察你将0(一个int)传递到第二级function()

    这种方式执行完全匹配,function(T, int),如果可用(即:如果T 支持请求的方法)。否则,执行可用的通用版本function(T, long)

    【讨论】:

      【解决方案3】:

      类似于max66's approach,但有返回类型检查:

      namespace detail
      {
          template <typename T>
          std::false_type has_feature_set_helper(...); // fallback if anything goes wrong in the helper function below
      
          template <typename T>
          auto has_feature_set_helper(int) -> std::conjunction<
              std::is_invocable_r<int, decltype(&T::Value1), T>, // assert that Value1 is a member function that returns something that is convertible to int
              std::is_invocable_r<char, decltype(&T::Value2), T> // assert that Value2 is a member function that returns something that is convertible to char
          >;
      }
      
      template <typename T>
      constexpr bool has_feature_set = decltype(detail::has_feature_set_helper<T>(0))::value;
      
      template <typename T>
      auto function(T record) -> std::enable_if_t<has_feature_set<T>>
      {
          std::cout << record.Value1() << " " << record.Value2() << std::endl;
      }
      

      Here 是一个完整的例子。

      请注意,返回类型检查并不严格,这意味着检查返回类型是否可转换为is_invocable_r_v 中指定的类型。

      【讨论】:

      • 不错。鉴于您已经开发了has_feature_set,当has_feature_set&lt;T&gt;false 时,您可以轻松添加启用的function() 版本。
      【解决方案4】:

      您可以设计一个从GenericPart 派生的中间模板类。然后,您可以将此模板专门用于 A 和 B。

      template<class T>
      class GenericPartTemplate : public GenericPart
      {
      public:
        int Value1() override;
        char Value2() override;
      }
      

      接下来不再需要将您的函数设为模板:

      void function(GenericPart& record) {
          std::cout << record.Value1() << " " << record.Value2() << std::endl;
      }
      

      如果您只想强制使用 A 和 B,您可以使用 type_traits 来检查编译时间 GenericPartTemplate 仅用于这些类型。

      【讨论】:

        猜你喜欢
        • 2016-12-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-21
        • 1970-01-01
        • 2011-07-04
        • 2021-10-17
        • 1970-01-01
        相关资源
        最近更新 更多