简体   繁体   English

模板函数没有继承的C++接口

[英]C++ interface without inheritance for template functions

There are two different classes with intersecting set of methods:有两个具有交叉方法集的不同类:

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() { };
}

The common part of the classes interface can be described like this:类接口的公共部分可以这样描述:

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

Please note that classes A and B come from some library and therefore cannot be inherited from GenericPart .请注意,类AB来自某个库,因此不能从GenericPart继承。

There is a template function that works with objects implementing methods described in GenericPart :有一个模板函数可以与实现GenericPart描述的方法的对象一起使用:

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

Is it possible to specialize this template function to make it receive only objects that match GenericPart ?是否可以专门化此模板函数以使其仅接收与GenericPart匹配的对象?

You could use the C++20 feature: concepts & constraints , but a simpler solution may involve static_assert .您可以使用 C++20 特性:概念和约束,但更简单的解决方案可能涉及static_assert See the comments in function for some explanation有关一些解释,请参阅function中的注释

#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
}

Is it possible to specialize this template function to make it receive only objects that match GenericPart?是否可以专门化此模板函数以使其仅接收与 GenericPart 匹配的对象?

You can't partially specialize a template function.您不能部分专门化模板函数。

But you can SFINAE enable/disable different version of it, according to the characteristicsf of T .但是您可以根据T的特性 SFINAE 启用/禁用它的不同版本。

By example, you can enable function() only for T supporting Value1() and Value2() methods as follows例如,您可以仅对支持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; }

It's more complicated disable a function() if doesn't support some methods (but see Timo's answer: given a has_feature_set become trivial) so I propose to add an unused parameter of some type (by example, int )如果不支持某些方法,则禁用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; }

and develop a generic function that accept an unused type of another (but compatible) type (by example, long ) and is ever enabled并开发一个通用函数,该函数接受另一种(但兼容)类型(例如long )的未使用类型并且永远启用

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

Now you can write a first level function() as follows现在你可以写一个第一级的function()如下

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

Observe that you pass 0 (a int ) to the second level function() .观察到您将0 (a int ) 传递给第二级function()

This way is executed the exact match, function(T, int) , if available (that is: if T support the requested methods).这种方式执行精确匹配, function(T, int) ,如果可用(即:如果T支持请求的方法)。 Otherwise execute the generic version, function(T, long) , that is ever available.否则,执行可用的通用版本function(T, long)

Similar to max66's approach but with return type checking:类似于max66 的方法,但带有返回类型检查:

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 a full example.是一个完整的例子。

Note that the return type checking is not strict, meaning it's checked if the return type is convertible to the type specified in is_invocable_r_v .请注意,返回类型检查并不严格,这意味着检查返回类型是否可转换为is_invocable_r_v指定的类型。

You could design an intermediate template class derived from GenericPart .您可以设计一个从GenericPart派生的中间模板类。 This template you then specialize for A and B.然后,您可以专门针对 A 和 B 使用此模板。

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

Next it is no longer needed to make your function a template:接下来不再需要使您的函数成为模板:

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

If you want to enforce only using A and B you can use type_traits to check in compile time GenericPartTemplate is only used with those types.如果您只想强制使用 A 和 B,您可以使用type_traits来检查编译时 GenericPartTemplate 仅用于这些类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM