C++ Добавление перегруженных методов для определенных специализаций шаблона

У меня довольно интересная проблема: у меня есть два шаблонных класса. Один может принимать любой параметр шаблона, другой более специализированный (для этой игрушечной задачи мы скажем, что он должен принимать числа с плавающей запятой).

template< class T >
class CanBeAnything{ /*...*/ };

template< class T >
class MustBeFloat{ static_assert(is_floating_point<T>::value, ""); /*...*/ };

Теперь у меня есть другой шаблон класса Foo. Foo не имеет ограничений по параметру шаблона и функции foo, которая принимает CanBeAnything или MustBeFloat того же типа. Я надеюсь использовать явное создание шаблона здесь, поэтому я хочу только MustBeFloat перегрузка, чтобы существовать, когда мой параметр шаблона является плавающей точкой.

Кажется, самое простое решение - специализировать Foo, но мне не нравится идея дублирования интерфейса между двумя классами. Я придумал почти работающее решение CRTP, с одной проблемой, о которой я упомяну через минуту.

/* Traits object to get the value_type out of foo */
template<class FooType>
class FooTraits{};

/* Helper parent class with floating-point only methods */
template<class Derived, bool isFloatingPoint>
class FooSpecialization {}

template<class Derived>
class FooSpecialization<Derived, true>
{
   typedef typename FooTraits<Derived>::value_type value_type;
public:
   void foo( MustBeFloat<value_type> & x );
};

/* Front-end interface */
template<class T>
class Foo : public FooSpecialization< Foo<T>, is_floating_point<T>::value >
{
   typedef FooSpecialization< Foo<T>, is_floating_point<T>::value > Parent;
   typedef typename FooTraits< Foo<T> >::value_type value_type;
public:
   void foo( CanBeAnything<value_type> & x );
private:
   friend class Parent;
};

template<class T>
class FooTraits< Foo<T> >
   { public:  typedef T value_type; };

Так вот в чем проблема: как есть, звонки на foo( MustBeFloat<value_type> & ) скрыты в дочернем классе путем скрытия имени, и компилятор выдает ошибку "Нет соответствующего вызова метода foo". Если я добавлю строку using Parent::foo; чтобы свалить это, я получаю ошибку "foo не существует в родительском классе" при создании экземпляра с плавающей запятой Foo, так как метод не существует так далеко.

Есть идеи? Я согласен с тем, чтобы очистить все это решение, если доступно более элегантное / рабочее решение.

РЕДАКТИРОВАТЬ: Просто чтобы уточнить: я делаю явную реализацию здесь, поэтому мне нужно, чтобы метод существовал, только если у меня есть параметр шаблона с плавающей запятой.

template class Foo<int>;
template class Foo<float>;

Это создает КАЖДЫЙ член класса, поэтому методы, не основанные на определенных методах, не нужны.

РЕДАКТИРОВАТЬ 2: Хорошо, так что я подумал об этом. Вот решение, с которым я иду:

template<class T>
class Foo
{
public:
   template<class T2>
   void foo( MustBeFloat<T2> & x ){ static_assert( std::is_same<T,T2>::value, ""); /* ... */}
   void foo( CanBeAnything<T> & x ){ /* ... */ }
};

template class Foo<int>;
template class Foo<float>;
template void Foo::foo<float>(MustBeFloat<float> &);

И все это работает. Ура! Спасибо людям, которые помогли привести меня к этому решению и придумали другие, более изобретательные.

2 ответа

Решение

ХОРОШО. Так что я не полностью проверил это, но если у вас есть ответы, я либо прокомментирую, либо изменю это предложение. Но вот пример кода, который должен скомпилироваться в версии foo() в упрощенном тестовом примере и иметь специфичный для типа, который использует родительский класс:

template< typename T >
class TestClass
{
  typedef struct PlaceholderType {};

public:
  template< typename T2 >
  typename std::enable_if< !std::is_same<T2, PlaceholderType>::value && std::is_same<T, float>::value, void >::type MyFunc( T2 param ) { std::cout << "Float"; }

  template< typename T2 >
  typename std::enable_if< !std::is_same<T2, PlaceholderType>::value && !std::is_same<T, float>::value, void >::type MyFunc( T2 param ) { std::cout << "Non-float"; }
};


int main(int argc, char* argv[])
{
  TestClass<int> intClass; // should only have the MyFunc(int) version available
  TestClass<float> floatClass; // should only have the MyFunc(float) version available

  intClass.MyFunc(5); // should output "Non-float"
  intClass.MyFunc(5.0f); // should output "Non-float"
  floatClass.MyFunc(2.0f); // should output "Float"
  floatClass.MyFunc(2); // should output "Float"
}

Этого может быть достаточно:

template <class T, bool isFloat, class Other>
struct FooTraits;
template <class T, class Other>
struct FooTraits<T, true, Other> { typedef MustBeFloat<T> MaybeFloat; };
template <class T, class Other>
struct FooTraits<T, false, Other> { typedef Other MaybeFloat; };

template <class T>
class Foo
{
    template <class U> friend class FooTraits<U>;
    class PrivateType {};
public:
    typedef typename FooTraits<T,
                               std::is_floating_point<T>::value,
                               PrivateType>::MaybeFloat MaybeFloat;
    void foo(CanBeAnything<T>&);
    void foo(MaybeFloat&);
};

Если T затем с плавающей точкой MaybeFloat будет typedef для MustBeFloat<T>, В противном случае это будет частный класс Foo так что было бы невозможно для абонента foo() синтезировать lvalue этого типа.

Другие вопросы по тегам