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

Това инстанцира ВСЕКИ член на класа, така че методите, които разчитат на неинстанциране на определени методи, не могат да се използват.

EDIT2: Добре, значи прекалявах с това. Ето решението, което използвам:

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 е float, искате Foo да има както MustBeFloat, така и CanBeAnything версии на foo()? Ако обаче value_type не е float, тогава трябва да има само версията CanBeAnything на foo()?   -  person qeadz    schedule 01.05.2014
comment
@qeadz Това е намерението, да. Съжалявам, ако не е станало ясно от описанието.   -  person Hounddog    schedule 01.05.2014
comment
ДОБРЕ. Ами примерите, използващи foo и подобни, винаги са тъпи, така че може би това предложение няма да работи. Ако всичко, от което се нуждаете, е специализирана версия на foo() да присъства, когато value_type е float, тогава какво ще кажете изобщо да не произлизате от 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 само когато типът е float. Другата функция foo() може също да бъде шаблон и enable_if за случая, когато типът не е float.   -  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
Това донякъде решава проблем, различен от този, който правя аз... Всъщност изобщо не искам версия без float. Но вие ми дадохте идеята за решението, което използвам...просто шаблонирайте функцията, така че да не се инстанцира с останалата част от класа. Без активиране_при необходимост. - 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