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> &);

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


person Hounddog    schedule 01.05.2014    source источник
comment
Просто чтобы уточнить: когда value_type является числом с плавающей запятой, вы хотите, чтобы Foo имел версии foo() как MustBeFloat, так и CanBeAnything? Однако, если value_type не является числом с плавающей запятой, тогда у него должна быть только CanBeAnything версия foo() ?   -  person qeadz    schedule 01.05.2014
comment
@qeadz Это и есть цель, да. Извините, если из описания непонятно.   -  person Hounddog    schedule 01.05.2014
comment
ХОРОШО. Что ж, примеры с использованием foo и тому подобного всегда тупые, поэтому, возможно, это предложение не сработает. Если все, что вам нужно, это чтобы специализированная версия foo() присутствовала, когда value_type является числом с плавающей запятой, то как насчет того, чтобы вообще не производным от FooSpecialization. Просто имейте несколько версий функции foo() внутри класса Foo. Используйте enable_if, чтобы скомпилировать только те версии, которые вам нужны, на основе value_type. Я могу напечатать это в ответе с фрагментом кода, если это будет понятнее.   -  person qeadz    schedule 01.05.2014
comment
...Честно говоря, я впервые слышу об использовании enable_if. Я сделал макет, и это ИМЕННО то, что мне нужно. Если вы хотите скопировать и вставить это как фактический ответ, я отмечу его как правильный.   -  person Hounddog    schedule 01.05.2014
comment
Ваша проблема решена, поэтому я теперь менее склонен тратить время на выполнение движений. Если кто-то опубликует более подробный ответ, отметьте его как правильный - ответы хорошего качества требуют времени и хороши для этого сайта в целом (мне просто лень делать их все время).   -  person qeadz    schedule 01.05.2014
comment
На самом деле, я только что реализовал его, и он у меня не работает, по крайней мере, не так, как я его использую — возможно, есть лучший способ использовать его, чем я. Моя мысль заключалась в том, чтобы сделать что-то вроде void foo(MustBeFloat‹value_type› & x, typename std::enable_if‹ std::is_floating_point::value ›::type * y = 0); К сожалению, при явном создании экземпляра это вызывает ошибку компиляции при инициализации Foo‹float›. Есть ли лучший способ сделать это, чем то, что я пытался?   -  person Hounddog    schedule 01.05.2014
comment
Недостатки быстрых комментариев :) Я предполагал, что функция будет шаблоном, чтобы она могла использовать enable_if. Затем вы бы только enable_if, когда тип является плавающим. Другая функция foo() может также быть шаблоном и enable_if для случая, когда тип не является плавающим.   -  person qeadz    schedule 01.05.2014
comment
Я немного поработаю над примером, чтобы опубликовать его, однако вскоре у меня назначена встреча, так что наберитесь терпения — это может занять пару часов.   -  person qeadz    schedule 01.05.2014
comment
Это нормально, не торопитесь, очевидно. Я немного повозился с ним, и мне кажется, по крайней мере, независимо от того, как я его шаблонизирую, enable_if не будет работать для меня, потому что цель состоит в том, чтобы вызвать ошибки компиляции, если я создаю экземпляр функции, которая является неправильной . В моем случае я явно создаю экземпляры класса шаблона Foo‹int› и класса шаблона Foo‹double›, которые пытаются создать экземпляры всех своих членов и бросают мне вызов при попытке создать экземпляр foo( MustBeFloat ).   -  person Hounddog    schedule 01.05.2014
comment
Ваша первая попытка сработает, если вы просто переместите foo(CanBeAnything<value_type>&) в FooSpecialization.   -  person Oktalist    schedule 01.05.2014
comment
ХОРОШО. Я написал быстрый фрагмент кода. Сейчас я должен быть на собрании, так что это действительно упрощено, но, надеюсь, его можно экстраполировать на вашу более сложную ситуацию.   -  person qeadz    schedule 01.05.2014
comment
@Oktalist Но тогда мне пришлось бы определять интерфейс дважды, один раз для базового случая и один для специализированного случая. Хотя это и не самое худшее в мире, я надеялся на решение, не связанное с этим.   -  person Hounddog    schedule 01.05.2014
comment
Или вы можете добавить еще один уровень наследования и переместить foo(CanBeAnything<value_type>&) в FooSpecializationBase. Шесть одних, полдюжины других.   -  person Oktalist    schedule 01.05.2014
comment
Это еще один хороший способ сделать это. Спасибо за идею.   -  person Hounddog    schedule 01.05.2014


Ответы (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"
}
person qeadz    schedule 01.05.2014
comment
Твоя магия работает. Я собираюсь выбрать этот ответ как правильный, поскольку вы предложили его первым, но любой, кто будет читать это в будущем, должен взглянуть на другие решения, предложенные Oktalist, потому что они также решают проблему. - person Hounddog; 01.05.2014
comment
Всего одна секунда. Я улучшил его (пицца все еще прибывает на нашу обеденную встречу, так что у меня было время). Эта версия, о которой я упоминаю, все еще технически неверна. Сейчас я заменяю ответ обновленной версией. - person qeadz; 01.05.2014
comment
Это своего рода решение другой проблемы, чем я делаю... Я действительно не хочу, чтобы версия без плавающей точки была доступна вообще. Но вы дали мне идею решения, которое я использую... просто создайте шаблон функции, чтобы она не создавалась вместе с остальной частью класса. Нет enable_if необходимо. - person Hounddog; 01.05.2014

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

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 этого типа.

person Oktalist    schedule 01.05.2014
comment
Это действительно умно. Я рассмотрю решение enable_if, но это интересный способ избежать проблемы. И это работает, что тоже полезно. - person Hounddog; 01.05.2014