Russian Qt Forum
Май 21, 2024, 14:32 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 [2] 3 4   Вниз
  Печать  
Автор Тема: Специализация шаблонного метода  (Прочитано 24839 раз)
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #15 : Февраль 19, 2016, 12:14 »

Простых, наверное, нет. С++ не поддерживает частичную специализацию шаблонных функций. Так что вариантов несколько:

К сожалению, распространенное заблуждение насчет "неподдерживания". Еще хуже, что каждый компилятор заблуждается по разному Улыбающийся

1. Частично специализировать весь класс целиком

Это можно, но тогда придется менять и наследников, а их многовато Грустный

2. Сделать сам оператор шаблонным и полностью специализировать его для float и double и всех возможных значений Size (я полагаю, их немного)

Это не реально - значений может быть много, очень много.

3. Вынести сравнение в отдельный класс-стратегию, и вызывать метод этого класса из оператора. Для выбора стратегии по умолчанию можно использовать std::conditional<std::is_floating_point<...>::value...>

Боюсь, не пройдет код-ревью Улыбающийся

4. Просто поставить условие, как указал Igors. Компилятор уберет ненужную ветку из соответствующей интстанциации как dead code.

Идея хорошая - но там та же проблема, что в п. 2...

5. Еще можно заморочиться со SFINAE и как-то сделать так, чтобы инстанциировалась только одна перегрузка для operator== в зависимости от типа элемента.

Ну, это будет непереносимо, думаю...

Но в любом случае, спасибо за советы)
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #16 : Февраль 19, 2016, 12:19 »

И что? Это дешево, удобно, практично. Нет никаких оснований отказываться от простого кода. Гляньте как в Qt сделано сравнение QVariant   

"Иногда, когда я смотрю код Qt, мне хочется взять в руки ружье Улыбающийся"

Надеюсь Racheengel позволит мне чуть "копнуть". Вот те же флоты потребовалось сравнивать с другими типами (хоть int), и по-разному, напр "точное сравнение", "fuzzy" и с каким-то фиксированным "epsilon". У меня проблем нет - да, подрисовал if/else, добавил GetCompareMode и SetCompareMode - готовченко!

Эх, как-то помнится, была подобная тема, про сравнения Улыбающийся
Имхо лучшим вариантом было бы обязать компилор генерировать правильный код для оператора == в зависимости от операндов. И добавить еще один оператор "побитного" сравнения (это для эстетов) - что-то типа "===" или подобного) чтоб выглядел страшно и сразу бросался в глаза, как что-то не очень хорошее Улыбающийся
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
kramer
Гость
« Ответ #17 : Февраль 19, 2016, 12:47 »

К сожалению, распространенное заблуждение насчет "неподдерживания". Еще хуже, что каждый компилятор заблуждается по разному Улыбающийся
В стандарте этого нет, значит C++ это не поддерживает. Я ничего не говорил про компиляторы - в GCC 4.9, по-моему, есть соответствующий экстеншн, а вот 2013-ая студия ругается.

Это можно, но тогда придется менять и наследников, а их многовато Грустный
Зачем? Наследники переопределяют operator==?

Это не реально - значений может быть много, очень много.
Ну, в таком случае, я думаю, не стоит использовать Size как шаблонный параметр. Code bloat, вот это все.

3. Вынести сравнение в отдельный класс-стратегию, и вызывать метод этого класса из оператора. Для выбора стратегии по умолчанию можно использовать std::conditional<std::is_floating_point<...>::value...>
Боюсь, не пройдет код-ревью Улыбающийся
Ну, вам видней, конечно, но если грамотно все сделать - не вижу никаких проблем, оверхеда нет, модифицировать библиотеку для добавления новых типов элементов не нужно, если нужно расширить стратегию - клиентский код просто специализирует ее для своего типа в своем же коде. Или у вас в гайдлайнах указано "Никаких стратегий, Александреску - враг народа!"?

4. Просто поставить условие, как указал Igors. Компилятор уберет ненужную ветку из соответствующей интстанциации как dead code.
Идея хорошая - но там та же проблема, что в п. 2...
Т.е. у вас предполагается множество разнообразных типов элементов с разнообразной же логикой оператора сравнения, а не только Fuzzy/Direct Comparison? Тогда стратегии, однозначно, хрестоматийный случай. Улыбающийся

5. Еще можно заморочиться со SFINAE и как-то сделать так, чтобы инстанциировалась только одна перегрузка для operator== в зависимости от типа элемента.
Ну, это будет непереносимо, думаю...
Почему же? Принцип SFINAE как раз является частью стандарта. Хотя, вообще, вы правы - компиляторы понимают его несколько по-разному, но я уверен, что можно будет подобрать некий общий знаменатель, который будет работать на всех требуемых вам платформах.
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #18 : Февраль 19, 2016, 13:15 »

Т.е. у вас предполагается множество разнообразных типов элементов с разнообразной же логикой оператора сравнения, а не только Fuzzy/Direct Comparison? Тогда стратегии, однозначно, хрестоматийный случай. Улыбающийся

Для классов у нас переопределен ==, но для простых типов достаточно будет Fuzzy/Direct Comparison.

Ну, вам видней, конечно, но если грамотно все сделать - не вижу никаких проблем, оверхеда нет, модифицировать библиотеку для добавления новых типов элементов не нужно, если нужно расширить стратегию - клиентский код просто специализирует ее для своего типа в своем же коде. Или у вас в гайдлайнах указано "Никаких стратегий, Александреску - враг народа!"?

У нас в гайдлайнах написано что-то вроде "Avoid unnecessary overhead" - а Александреску сам по себе ходячий оверхед Улыбающийся

Почему же? Принцип SFINAE как раз является частью стандарта. Хотя, вообще, вы правы - компиляторы понимают его несколько по-разному, но я уверен, что можно будет подобрать некий общий знаменатель, который будет работать на всех требуемых вам платформах.

По поводу шаблонов и перегрузок, кстати, понравилась цитата одного товарища с хабры:

Цитировать
Круто. Но все-же очень жаль, что С++ в какой-то момент стал развиваться в таком извращенном направлении. Теперь это не остановить, и на шаблонах пишут уже почти все что угодно вплоть до нетривиальных вычислений времени компиляции и парсеров. К сожалению.
А ведь по сути они задумывались лишь как возможность написания универсальных функций и структур данных, т.е. чтобы не писать например отдельные классы списка для int, float и string.
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #19 : Февраль 19, 2016, 13:49 »

Мне вот что интересно: если в этом шаблоне параметр Element по умолчанию равен double, т.е. предполагается, что этот класс чаще всего будет использоваться именно с double, то почему там изначально "неправильное" сравнение для double? И какое "правильное"? По идее вопрос должен быть в другом: "В этом шаблоне какой-то дурак указал параметр по умолчанию double и приделал qFuzzyCompare. Это что, теперь и для целочисленных типов надо qFuzzyCompare использовать? И для других тоже?".

Хотя нормальные вопросы, которыми на мой взгляд стоит задаться, такие:
1. Почему для элементов типа double в этом классе не подходит "общепринятая" операция сравнения? И какая операция сравнения в данном случае (для данного класса) "правильная"?
2. Какими ещё типами может специализироваться шаблон? Какая для них "правильная" операция сравнения?

Можно ли из этих вопросов сделать вывод, что для класса TVector используется какая-то особенная именно для него операция сравнения элементов? Может в этом направлении и надо копать? Я бы предложил использовать вариант m_ax, или перегружать отдельную функцию для сравнения элементов вектора (типа qFuzzyCompare), если остальной алгоритм сравнения одинаков.
Записан

Пока сам не сделаешь...
kramer
Гость
« Ответ #20 : Февраль 19, 2016, 14:10 »

Для классов у нас переопределен ==, но для простых типов достаточно будет Fuzzy/Direct Comparison.
Ну распрекрасно, тогда для способа с ветвлениями в коде - как-то так:
Код
C++ (Qt)
bool operator==(TVector<Size, T> const& other)
{
   if ( std::is_class<T>::value )
   {
     //loop through elements with T::operator==
   } else if ( std::is_floating_point<T>::value )
         {
           //loop through elements with qFuzzyCompare
         } else if ( std::is_integral<T>::value )
              {
               //do something else with integrals, or alternatively merge this with the first branch
              }
}
 
Работать будет, но расширяемость никакая, если потребуется для какого-то из классов элементов переопределить логику оператора сравнения контейнера, то придется все эти else-if перелопачивать. А вот со стратегиями - запросто - просто специализируете класс стратегии для типа элемента - и вперед. Конечно, в этом случае std::conditional уже не проканает, придется добавить еще класс характеристик элемента, который будет подбирать стратегию, наиболее ему соответствующую.

У нас в гайдлайнах написано что-то вроде "Avoid unnecessary overhead" - а Александреску сам по себе ходячий оверхед Улыбающийся
Рантайм оверхед там нулевой, все вызовы методов стратегий инлайнятся. Компиляцию это замедлит, конечно, да и читабельности и понятности кода это добро совсем не добавляет, но тут уж такое дело - шаблоны - штука непростая.
По поводу шаблонов и перегрузок, кстати, понравилась цитата одного товарища с хабры:
Да ради бога, эта первоначальная функциональность никуда не делась, никто вам не мешает писать обобщенный класс для всех, и специализировать его для float и double, коль скоро вам потребовалась другая логика.
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #21 : Февраль 19, 2016, 14:36 »

Мне вот что интересно: если в этом шаблоне параметр Element по умолчанию равен double, т.е. предполагается, что этот класс чаще всего будет использоваться именно с double, то почему там изначально "неправильное" сравнение для double? И какое "правильное"? По идее вопрос должен быть в другом: "В этом шаблоне какой-то дурак указал параметр по умолчанию double и приделал qFuzzyCompare. Это что, теперь и для целочисленных типов надо qFuzzyCompare использовать? И для других тоже?".

Нет, наоборот - по умолчанию для всех типов использовался стандартный ==, так как TVector предназначен для любых типов, как примитивных, так и нет. qFuzzyCompare понадобился потом, для даблов и флотов.

1. Почему для элементов типа double в этом классе не подходит "общепринятая" операция сравнения? И какая операция сравнения в данном случае (для данного класса) "правильная"?

http://ru.stackoverflow.com/questions/399420/%D0%91%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE-%D0%BB%D0%B8-%D1%81%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B4%D0%BB%D1%8F-%D1%82%D0%B8%D0%BF%D0%B0-double

2. Какими ещё типами может специализироваться шаблон? Какая для них "правильная" операция сравнения?

Пока что интересуют double и float, т.к. для классов обычно оператор сравнения переопределен.

Можно ли из этих вопросов сделать вывод, что для класса TVector используется какая-то особенная именно для него операция сравнения элементов? Может в этом направлении и надо копать? Я бы предложил использовать вариант m_ax, или перегружать отдельную функцию для сравнения элементов вектора (типа qFuzzyCompare), если остальной алгоритм сравнения одинаков.

Алгоритм простой - сравнивается "каждый с каждым" до первого расхождения.
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #22 : Февраль 19, 2016, 14:40 »

Ну распрекрасно, тогда для способа с ветвлениями в коде - как-то так:
Код
C++ (Qt)
 

Да, походу придется что-то такое и сделать, наверное. Выглядит, конечно, кривенько, но хотя бы ревью пройдет)
 
Рантайм оверхед там нулевой, все вызовы методов стратегий инлайнятся. Компиляцию это замедлит, конечно, да и читабельности и понятности кода это добро совсем не добавляет, но тут уж такое дело - шаблоны - штука непростая.

Template cancer Улыбающийся это не пройдет, т.к - нельзя нарушать принцип KISS.
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
kramer
Гость
« Ответ #23 : Февраль 19, 2016, 14:54 »

Да, походу придется что-то такое и сделать, наверное. Выглядит, конечно, кривенько, но хотя бы ревью пройдет)


Пожалуй, лучше сделайте как ViTech предложил, через перегрузку:
Код
C++ (Qt)
template<class T> bool tVectorElementCompare(T const& a, T const& b)
{
   std::cout << "Generic comparison, using T::operator==" << std::endl;
   return a == b;
}
 
bool tVectorElementCompare(double const& a, double const& b)
{
   std::cout << "Double comparison, using fuzzy compare" << std::endl;
   return qFuzzyCompare(a,b);
}
 
bool tVectorElementCompare(int const& a, int const& b)
{
   std::cout << "Int comparison, using direct compare" << std::endl;
   return a == b;
}
 
 
template<int Size, class T>
struct TVector {
   bool operator==(TVector const& other)
   {
       for (int i=0; i<Size; ++i)
           if ( !tVectorElementCompare(data[i], other.data[i]) )
               return false;
       return true;
   }
 
   T data[Size];
};
 
struct MyElem {
   bool operator==(MyElem const& other) const
   {
       return true;
   }
};
 
int main(int, char**)
{
   TVector<1, double> dvec1, dvec2;
   TVector<1, int> ivec1, ivec2;
   TVector<1, MyElem> cvec1, cvec2;
 
   std::cout << ( dvec1 == dvec2 ) << std::endl;
   std::cout << ( ivec1 == ivec2 ) << std::endl;
   std::cout << ( cvec1 == cvec2 ) << std::endl;
 
   return 0;
}
 
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #24 : Февраль 19, 2016, 15:06 »

Нет, наоборот - по умолчанию для всех типов использовался стандартный ==, так как TVector предназначен для любых типов, как примитивных, так и нет. qFuzzyCompare понадобился потом, для даблов и флотов.

...

Пока что интересуют double и float, т.к. для классов обычно оператор сравнения переопределен.

Мой основной посыл в том, что по каким-то причинам (вполне рациональным), в классе TVector необходима своя отдельная операция по сравнению элементов, когда нельзя полагаться на "общепринятые" сравнения. Значит надо добавить операцию сравнения элементов именно для этого класса (либо для семейства классов, пространства имён). И пока набрасывал пример кода, kramer опередил и написал именно то, что я и хотел предложить Улыбающийся. В простом случае, если надо обеспечить qFuzzyCompare для вещественных типов, можно обойтись перегрузкой функции. Для более сложных и обобщённых случаев можно шаблонный класс для сравнения завести, и функцию для облегчения доступа к нему.

Соответственно "способ с ветвлениями в коде - как-то так:" я бы использовать не стал, kramer объяснил почему, я это поддерживаю.
Записан

Пока сам не сделаешь...
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #25 : Февраль 19, 2016, 18:21 »

В связи с отсутствием у меня VC, нашел сайт с возможностью online-компиляции: http://webcompiler.cloudapp.net/

Там для компиляции используется Visual C++ версии:
Compiler version: 19.00.23720.0 (x86). Last updated: Jan 20, 2016

Следующий код спокойно собирается:
Код
C++ (Qt)
#include <iostream>
 
using namespace std;
 
template<int Size, class T>
struct TVector
{
T data[Size];
};
 
template<int Size, typename T>
bool operator ==( const TVector<Size, T> &, const TVector<Size, T> & )
{
cout << "operator== for T" << endl;
return false;
}
 
template<int Size>
bool operator ==( const TVector<Size, double> &, const TVector<Size, double> & )
{
cout << "operator== for double" << endl;
return false;
}
 
struct MyElem {
bool operator==(MyElem const& /*other*/) const
{
return true;
}
};
 
int main(int, char**)
{
TVector<1, double> dvec1, dvec2;
TVector<1, int> ivec1, ivec2;
TVector<1, MyElem> cvec1, cvec2;
 
std::cout << ( dvec1 == dvec2 ) << std::endl;
std::cout << ( ivec1 == ivec2 ) << std::endl;
std::cout << ( cvec1 == cvec2 ) << std::endl;
 
return 0;
}
 
Записан
kramer
Гость
« Ответ #26 : Февраль 19, 2016, 20:20 »

Там для компиляции используется Visual C++ версии:
Compiler version: 19.00.23720.0 (x86). Last updated: Jan 20, 2016
Следующий код спокойно собирается:
Ну, не всем же так повезло, что есть возможность использовать распоследние версии компиляторов. Я, например, на VS 2012 (это 17.0) только в прошлом году перешел, а до того сидел на 2008-ой. И под линуксом тоже - GCC 4.1 за радость.

==dork mode on==
И все равно, это не С++. Это расширение компилятора, тем более майкрософтовское. Без ключа /Za что-нибудь кросс-платформенное писать - это танцы на граблях.
==dork mode off==
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #27 : Февраль 19, 2016, 20:26 »

Без ключа /Za что-нибудь кросс-платформенное писать - это танцы на граблях.
Добавил этот флаг на сайте - все равно собирается (не считает vc это расширением). Улыбающийся
А то что он это не собирал раньше говорит только о его убогость. Улыбающийся

Мне лень сейчас ковыряться в стандарте, но... в C++ нет частичной специализации функций, потому что есть возможность их перепределять.
« Последнее редактирование: Февраль 19, 2016, 20:54 от Old » Записан
kramer
Гость
« Ответ #28 : Февраль 19, 2016, 21:10 »

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

Мне лень сейчас ковыряться в стандарте, но... в C++ нет частичной специализации функций, потому что есть возможность их перепределять.
Совершенно верно, и, как мне кажется, совершенно оправданно. Усложнять и без того супермозговзрывающие правила перегрузки функций еще и частично специализированными функциями - по-моему, это перебор. Впрочем, это уже оффтопик.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #29 : Февраль 19, 2016, 21:13 »

Совершенно верно, и, как мне кажется, совершенно оправданно. Усложнять и без того супермозговзрывающие правила перегрузки функций еще и частично специализированными функциями - по-моему, это перебор. Впрочем, это уже оффтопик.
Мне кажется в нашем случае перегрузка и срабатывает, а не частичная специализация.
Записан
Страниц: 1 [2] 3 4   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.231 секунд. Запросов: 22.