Интерфейс C ++ без наследования для шаблонных функций - PullRequest
3 голосов
/ 03 марта 2020

Существует два разных класса с пересекающимся набором методов:

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

Обратите внимание, что классы A и B происходят из некоторой библиотеки и поэтому не могут быть унаследованы от GenericPart.

Существует функция шаблона, которая работает с объектами, реализующими методы, описанные в GenericPart:

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

Можно ли специализировать эту функцию шаблона, чтобы она принимала только объекты, которые соответствуют GenericPart?

Ответы [ 4 ]

2 голосов
/ 03 марта 2020

Аналогично подходу 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;
}

Здесь - полный пример.

Обратите внимание, что возвращаемое значение проверка типов не является строгой, то есть проверяется, может ли возвращаемый тип быть преобразованным в тип, указанный в is_invocable_r_v.

2 голосов
/ 03 марта 2020

Можно ли специализировать эту функцию шаблона, чтобы она принимала только объекты, соответствующие GenericPart?

Нельзя частично специализировать функцию шаблона.

Но вы можете SFINAE включать / отключать другую его версию в соответствии с характеристиками f T.

. Например, вы можете включить function() только для T, поддерживающих Value1() и Value2() методы, как следует

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

Сложнее отключить function(), если не поддерживает некоторые методы (но см. ответ Тимо: учитывая, что 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; }

и разработать обобщенную функцию c, которая принимает неиспользуемый тип другого (но совместимого) типа (например, 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 (a int) на второй уровень function().

Таким способом выполняется точное совпадение, function(T, int), если доступно (то есть, если T поддерживает запрошенные методы). В противном случае выполните версию generi c, function(T, long), которая всегда доступна.

1 голос
/ 03 марта 2020

Вы можете использовать функцию C ++ 20: концепции и ограничения , но более простое решение может включать static_assert. См. Комментарии в 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
}
1 голос
/ 03 марта 2020

Вы можете создать промежуточный класс шаблона, полученный из 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 используется только с этими типами.

...