Страницы

Поиск по вопросам

пятница, 13 декабря 2019 г.

Как определить тип typename в template на этапе компиляции?

#cpp #шаблоны_с++


Есть проблемный шаблонный метод который выполняет простые действия с объектами типа
T ( например += или + или любой другой ) 

но для некоторых типов этот оператор не определен и компилятор выдает ошибку:


  error C2679: binary '+=' : no operator found which takes a right-hand
  operand of type 'Некоторый_тип'


template < typename T >
T Foo ( const T & value )
{
   T buf += value;
   return buf;
}


я могу использовать !std::is_same  чтобы определить тип и решить
что с ним делать, но компилятор не понимает что я решаю эту проблему сам и ругается
когда я использую метод с типом без определения += как здесь: Foo < Тот_самый_тип >
( значение );

есть ли возможность на этапе компиляции отсечь исключительные ситуации?

что-то вроде 

template < typename T >
T Foo ( const T & value )
{
#if T Тот_самый_тип
           T buf = value;
#else
           T buf += value;
#endif
           return buf;
}


я понимаю что могу сделать специализацию для шаблона, но делать ее из-за одной строчки
кажется не оптимальным 

PS: вариант должен подходить и для gcc и для msvs2010
    


Ответы

Ответ 1



Если вам нужно различить, есть ли в типе T оператор +, вам придётся заняться метапрограммированием на шаблонах, известном как «шаблонная магия». Итак, начнём. Во-первых, напишем вспомогательную структуру, которая определяет доступность сложения: #include template class has_addition { // Эта функция скомпилируется только если выражение // std::declval() + std::declval() // имеет смысл. То есть, выражение t1 + t1, где // t1 и t2 имеют тип C. template static std::true_type test( decltype( std::declval() + std::declval() )* ); // Эта функция скомпилируется всегда template static std::false_type test(...); public: // test(0) есть вызов первой функции, если она скомпилировалась // и второй, если нет. аргумент 0 подходит в обоих случаях, // так как первая функция получает указатель // соответственно decltype(test(0)) будет true_type // или false_type, а value - true или false // случай, когда первая функция не компилируется, работает из-за // принципа SFINAE enum { value = decltype(test(0))::value }; }; Отлично, мы на полпути к решению. Теперь осталось воспользоваться им: #include template class X { public: // воспользуемся опять SFINAE, для этого применим стандартный // шаблон enable_if. SFINAE работает лишь на шаблонных функциях, // поэтому добавим фиктивный шаблон // это скомпилируется только если has_addition::value == false // то есть если значения типа T нельзя складывать template typename std::enable_if::value, void>::type use(T1 t) { std::cout << "without addition" << std::endl; T1 tt = t; } // а это скомпилируется только если has_addition::value == true // то есть если значения типа T можно складывать template typename std::enable_if::value, void>::type use(T1 t) { std::cout << "with addition" << std::endl; auto tt = t + t; } }; Всё! Вот такой код: int main() { X x1; x1.use(1); X x2; x2.use(nullptr); return 0; } выдаёт with addition without addition Обновление: К сожалению, компилятор Visual Studio 2010 не настолько продвинут, так что код пришлось упростить и сделать более прямолинейным. (Для Visual Studio 2013 переделки не нужны.) Вот результат: // precompiled header Visual Studio #include "stdafx.h" #include #include template class has_addition { // не поддерживается decltype в enum-константе -> откатываемся на трюк с размерами typedef char yes[1]; typedef char no[2]; // нету declval, эмулируем вручную. сама функция, понятно, не нужна template static C generateValue(); template static yes& test( decltype( generateValue() + generateValue() )* ); template static no& test(...); public: enum { value = sizeof(test(0)) == sizeof(yes) }; }; template class X { public: // нельзя использовать значения параметров шаблона по умолчанию // используем явное указание шаблонного параметра, прячем в дополнительную функцию void use(T t) { use_impl(t); } private: template typename std::enable_if::value, void>::type use_impl(T1 t) { std::cout << "without addition" << std::endl; T1 tt = t; } template typename std::enable_if::value, void>::type use_impl(T1 t) { std::cout << "with addition" << std::endl; auto tt = t + t; } }; int main() { X x1; x1.use(1); X x2; x2.use(nullptr); return 0; } Наверняка @Abyx придумает решение поизящнее.

Ответ 2



Если я правильно понял, то Вы можете исключить неугодный тип с помощью std::is_same . Тогда Ваша проблема может быть решена с помощью std::enabled_if: template < typename T > typename std::enable_if::value, T>::type Foo(const T & value) { T buf = value; return buf; } template < typename T > typename std::enable_if::value, T>::type Foo(const T & value) { T buf += value; return buf; } P.S. Я не проверял на MSVC10, так как не имею его.

Ответ 3



Правильный подход это специализация. Если из-за нее много копипаста, стоит подумать о рефакторинге и\или применить обходные маневры. Если с рефакторингом не получается, то... все равно правильно использовать специализацию. Ну и совсем в клининических случаях допустимо применять макросы, но это бывает совсем редко, в моей практике было один раз. Не надо специализировать всю большую функцию, спициализируйте только одну строчку! Я часто использую такой ход: template void SpecialSmallFunction(T value) { // обобщенные операции } template<> void SpecialSmallFunction(Foo value) { // сециализированные для Foo операции } template void BigFunction(T value) { // много обобщенного кода SpecialSmallFunction(value); // много обобщенного кода } Смысл в том, что для того чтобы не копипастить много обобщенного кода мы выносим специализированный код в маленькие функции которые проще поддерживать. Ваш случай самый простой - используйте специализацию.

Комментариев нет:

Отправить комментарий