Страницы

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

пятница, 19 октября 2018 г.

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

Есть проблемный шаблонный метод который выполняет простые действия с объектами типа 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


Ответ

Если вам нужно различить, есть ли в типе 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 придумает решение поизящнее.

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

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