C ++ с помощью объявления для пакета параметров - PullRequest
3 голосов
/ 05 апреля 2020

Я хотел бы определить класс, который наследуется от группы классов, но который не скрывает некоторые специфические c методы от этих классов.

Представьте себе следующий код:

template<typename... Bases>
class SomeClass : public Bases...
{
public: 
  using Bases::DoSomething...;

  void DoSomething(){
    //this is just another overload
  }
};

Проблема теперь в том, что только в одном классе нет члена с именем DoSomething Я получаю ошибку. Я уже пытался эмулировать «игнорировать, если не определено» с помощью макроса и SFINAE, но для обработки всех случаев это становится очень большим и уродливым! У вас есть идея, чтобы решить эту проблему?

Было бы очень хорошо, если бы я мог определить: «Эй, используя - игнорировать пропущенные элементы».

Здесь у меня есть пример кода: Godbolt

Ответы [ 4 ]

3 голосов
/ 06 апреля 2020

Проблема с подходом Jarod42 заключается в том, что вы меняете то, как выглядит разрешение перегрузки - как только вы сделаете все шаблоном, тогда все станет точным совпадением, и вы больше не сможете различать несколько жизнеспособных кандидатов:

struct A { void DoSomething(int); };
struct B { void DoSomething(double); };
SomeClass<A, B>().DoSomething(42); // error ambiguous

Единственный способ сохранить разрешение перегрузки - использовать наследование.

Ключ к завершению sh того, что ecatmur запущено. Но как выглядит HasDoSomething? Подход в ссылке работает только в том случае, если существует один не перегруженный шаблон. Но мы можем сделать лучше. Мы можем использовать тот же механизм, чтобы определить, существует ли DoSomething, для которого требуется using для начала: имена из разных областей не перегружаются.

Итак, мы вводим новый базовый класс который имеет DoSomething, который никогда не будет выбран по-настоящему - и мы делаем это, создавая наш собственный явный тип тега, который мы единственные, кто когда-либо будет создавать. Из-за отсутствия лучшего имени, я назову его в честь моей собаки, которая является Вестом ie:

struct westie_tag { explicit westie_tag() = default; };
inline constexpr westie_tag westie{};
template <typename T> struct Fallback { void DoSomething(westie_tag, ...); };

И сделаю это variadi c для хорошей меры, просто чтобы сделать это как минимум. Но это не имеет значения. Теперь, если мы введем новый тип, такой как:

template <typename T> struct Hybrid : Fallback<T>, T { };

Тогда мы можем вызвать DoSomething() на гибриде именно тогда, когда T делает не с перегрузкой DoSomething - любого вида. Это:

template <typename T, typename=void>
struct HasDoSomething : std::true_type { };
template <typename T>
struct HasDoSomething<T, std::void_t<decltype(std::declval<Hybrid<T>>().DoSomething(westie))>>
    : std::false_type
{ };

Обратите внимание, что обычно в этих чертах основной является false, а специализация true - здесь все наоборот. Ключевое различие между этим ответом и ответом ecatmur заключается в том, что резервная перегрузка все равно должна быть каким-то образом вызываемой - и использовать эту возможность для ее проверки - просто она не будет фактически вызываемой для любого типа, который пользователь будет фактически использовать.

Проверка таким образом позволяет нам правильно определить, что:

struct C {
    void DoSomething(int);
    void DoSomething(int, int);
};

действительно удовлетворяет HasDoSomething.

И затем мы используем тот же метод, который показал ecatmur:

template <typename T>
using pick_base = std::conditional_t<
    HasDoSomething<T>::value,
    T,
    Fallback<T>>;

template<typename... Bases>
class SomeClass : public Fallback<Bases>..., public Bases...
{
public: 
  using pick_base<Bases>::DoSomething...;

  void DoSomething();
};

И это работает независимо от того, как выглядят все перегрузки Bases DoSomething, и правильно выполняет разрешение перегрузки в первом упомянутом мной случае.

Демо

1 голос
/ 06 апреля 2020

Как насчет условного использования запасного варианта?

Создание неотзываемых реализаций каждого метода:

template<class>
struct Fallback {
    template<class..., class> void DoSomething();
};

Наследовать от Fallback один раз для каждого базового класса:

class SomeClass : private Fallback<Bases>..., public Bases...

Затем выведите каждый метод условно из базового класса или из соответствующего запасного варианта:

using std::conditional_t<HasDoSomething<Bases>::value, Bases, Fallback<Bases>>::DoSomething...;

Пример .

1 голос
/ 05 апреля 2020

Вы можете добавить оболочку, которая обрабатывает базовые случаи c, переадресацию вместо using:

template <typename T>
struct Wrapper : T
{
    template <typename ... Ts, typename Base = T>
    auto DoSomething(Ts&&... args) const
    -> decltype(Base::DoSomething(std::forward<Ts>(args)...))
    {
        return Base::DoSomething(std::forward<Ts>(args)...);
    }

    template <typename ... Ts, typename Base = T>
    auto DoSomething(Ts&&... args)
    -> decltype(Base::DoSomething(std::forward<Ts>(args)...))
    {
        return Base::DoSomething(std::forward<Ts>(args)...);
    }

    // You might fix missing noexcept specification
    // You might add missing combination volatile/reference/C-elipsis version.
    // And also special template versions with non deducible template parameter...
};

template <typename... Bases>
class SomeClass : public Wrapper<Bases>...
{
public: 
  using Wrapper<Bases>::DoSomething...; // All wrappers have those methods,
                                        // even if SFINAEd

  void DoSomething(){ /*..*/ }
};

Демо

Как отметил Барри, есть другие недостатки, связанные с изменением разрешения перегрузки, что делает некоторые вызовы неоднозначными ...

Примечание. Я предложил это решение, поскольку не знал, как создать правильные признаки для обнаружения присутствия DoSomething во всех случаях (перегрузки). в основном проблема). Барри решил, что у вас есть лучшая альтернатива.

0 голосов
/ 06 апреля 2020

Вы можете реализовать это без дополнительных базовых классов, если вы готовы использовать шаблон псевдонима для имени вашего класса. Хитрость заключается в том, чтобы разделить аргументы шаблона на два пакета на основе предиката:

#include<type_traits>

template<class,class> struct cons;  // not defined
template<class ...TT> struct pack;  // not defined

namespace detail {
  template<template<class> class,class,class,class>
  struct sift;
  template<template<class> class P,class ...TT,class ...FF>
  struct sift<P,pack<>,pack<TT...>,pack<FF...>>
  {using type=cons<pack<TT...>,pack<FF...>>;};
  template<template<class> class P,class I,class ...II,
           class ...TT,class ...FF>
  struct sift<P,pack<I,II...>,pack<TT...>,pack<FF...>> :
    sift<P,pack<II...>,
         std::conditional_t<P<I>::value,pack<TT...,I>,pack<TT...>>,
         std::conditional_t<P<I>::value,pack<FF...>,pack<FF...,I>>> {};

  template<class,class=void> struct has_something : std::false_type {};
  template<class T>
  struct has_something<T,decltype(void(&T::DoSomething))> :
    std::true_type {};
}

template<template<class> class P,class ...TT>
using sift_t=typename detail::sift<P,pack<TT...>,pack<>,pack<>>::type;

Затем разложить результат и наследовать от отдельных классов:

template<class> struct C;
template<class ...MM,class ...OO>  // have Method, Others
struct C<cons<pack<MM...>,pack<OO...>>> : MM...,OO... {
  using MM::DoSomething...;
  void DoSomething();
};

template<class T> using has_something=detail::has_something<T>;

template<class ...TT> using C_for=C<sift_t<has_something,TT...>>;

Обратите внимание, что has_something здесь поддерживает только не перегруженные методы (для базового класса) для простоты; см. ответ Барри для обобщения этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...