3

Предположим следующую ситуацию:Как ограничить параметры шаблона потомком, который является специализацией шаблонного интерфейса?

  1. Там является шаблонный интерфейс, определяющий набор операций на различных типах данных.
  2. Этот интерфейс реализован различными специализированными классами, определяющими операции для фактических типов данных.
  3. Существует некоторый управляющий класс, который должен работать экземпляры классов, как это определено в 2

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

#include <iostream> 
#include <type_traits> 

template <typename R, typename S> 
class ICanDoIt 
{ 
    public: 
     virtual void doStuff() = 0; 

    protected: 
     ICanDoIt<R, S>(R rA, S sA) : r(rA), s(sA) {}; 
     R r; 
     S s; 
}; 

class DoesIt : public ICanDoIt<int, double> 
{ 
     public: 
      DoesIt(int iA, double dA) : ICanDoIt(iA, dA) {}; 

      virtual void doStuff() 
       { std::cout << "r * s = " << r * s << " done." << std::endl; } 
}; 

template <typename T> 
class NeedsSomeoneWhoCanDoIt 
{ 
    static_assert(std::is_base_of<ICanDoIt<R, S>, T>::value, 
        "T needs to be able to do it."); 

    public: 
     NeedsSomeoneWhoCanDoIt(const T& doesItA) : doesIt(doesItA) {}; 
     void getItDone() { doesIt.doStuff(); }; 

    private: 
     T doesIt; 
}; 


int main() 
{ 
    DoesIt doesIt(5, 2.2); 
    NeedsSomeoneWhoCanDoIt<DoesIt> needsIt(doesIt); 
    needsIt.getItDone(); 
} 

Если вы untemplate интерфейс «ICanDoIt» код будет работать. Но static_assert для шаблонной версии не сработает, потому что аргументы шаблона ICanDoIt обернуты и скрыты специализацией, выполняемой в декалировании DoIt.

Как можно ограничить управляющие классы (NeedsSomeoneWhoCanDoIt) параметры шаблона «T», чтобы любого специализации ICanDoIt, независимо от того, какого типа было выбрано для R, S во время специализации ICanDoIt?

ответ

2

Вы всегда можете сделать фактические типы для R и S используется для создания экземпляра ICanDoIt доступной для производного класса, т.е.

template <typename R, typename S> class ICanDoIt { 
public: 
    typedef R R_t; 
    typedef S S_t; 

    virtual void doStuff() = 0; 
}; 

, чтобы ваш static_assert стал бы

static_assert(std::is_base_of<ICanDoIt<typename T::R_t, typename T::S_t>, 
           T>::value, 
       "T needs to be able to do it."); 

В зависимости на чем выглядит ваш фактический код, дизайн может стать яснее, если вы определите чисто абстрактную базовую cla ss (т.е. фактический тип ICanDoItBase вместо шаблона), из которого вы наследуете текущую шаблонную функциональность в ICanDoIt, которая снова будет базой DoesIt.

NeedsSomeoneWhoCanDoIt может затем непосредственно использовать полиморфный базовый класс ICanDoItBase без каких-либо дополнительных проверок типа.

+0

Идеальное решение, работает как очарование. – norritt

+0

@norritt: Несмотря на то, что он работает со мной, ваш нынешний дизайн по-прежнему выглядит как странная химера полиморфизма времени исполнения и чрезмерно ограничительное воплощение понятий. –

+0

Его вид результат того, что, похоже, нет способа управлять шаблонами экземпляров класса с разными аргументами шаблона в одном контейнере, за исключением использования Boost (boost :: any). Это часть моего обходного пути с использованием STL означает, что я не совсем удовлетворен сам, но это лучшее, что я придумал. Листинг типа C# в стиле исполнения приведет к тому, что все эти проблемы исчезнут;) – norritt

2

Вам не нужно публиковать параметры шаблона. Стандартный подход на основе SFINAE будет работать очень хорошо.

namespace detail { 
    template<class R, class S> 
    std::true_type test(ICanDoIt<R, S>*); 
    std::false_type test(...); 
} 

template<class T> 
using can_do_it = decltype(detail::test((T*)nullptr));