Как вы думаете, является ли данное поведение перегрузки дебильным?
От: Eternity Россия  
Дата: 13.09.13 03:50
Оценка: -2 :)
Еще один WTF в языке C++. Но, может быть, как обычно, есть какое-то (псевдо)рациональное объяснение для этого?

Когда я пишу f(B), я жду, что вызовется f(A), потому что B это тоже A ("is a"). Даже если не брать это очевидное разумное обоснование, все-таки, перегрузка с производным классом как-то гораздо специфичнее, чем шаблонная перегрузка, принимающая тип без ограничений.

#include <iostream>

using namespace std;

class A {};

class B : public A {};

void f(const A& a) {
    cout << "f(A);" << endl;
}

template <class T>
void f(const T& t) {
    cout << "f(T);" << endl;
}

int main() {
    A a;
    f(a);
    B b;
    f(b);
    return 0;
}


Output:

f(A);
f(T); << WTF?

Re: перегрузка и шаблон
От: jazzer Россия Skype: enerjazzer
Дата: 13.09.13 04:11
Оценка: 5 (2) +1
Здравствуйте, Eternity, Вы писали:

E>Еще один WTF в языке C++. Но, может быть, как обычно, есть какое-то (псевдо)рациональное объяснение для этого?


E>Когда я пишу f(B), я жду, что вызовется f(A), потому что B это тоже A ("is a"). Даже если не брать это очевидное разумное обоснование, все-таки, перегрузка с производным классом как-то гораздо специфичнее, чем шаблонная перегрузка, принимающая тип без ограничений.


Наоборот, шаблон способен предоставить полное попадание void f(const B& t), в то время как f(A) потребует преобразования аргумента B->A и поэтому проиграет при разрешении перегрузки.

ЗЫ В следующий раз пиши осмысленные сабжи, плиз.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Как вы думаете, является ли данное поведение перегрузки дебильным?
От: dilmah США  
Дата: 13.09.13 04:23
Оценка: :))) :)
зал замер в ожидании. Лишь хруст попкорна пертурбировал звенящую тишину.
Re[2]: перегрузка и шаблон
От: Eternity Россия  
Дата: 13.09.13 04:34
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Наоборот, шаблон способен предоставить полное попадание void f(const B& t), в то время как f(A) потребует преобразования аргумента B->A и поэтому проиграет при разрешении перегрузки.


Странно, что мне это не приходило в голову. Видимо, таким объяснением и руководствовались разработчики языка.

С другой стороны, оно несколько алогичное. Ведь шаблонная перегрузка обеспечивает точное попадание и для f(a) тоже. Если руководствоваться точным попаданием, тогда нужно как-то обосновать, почему мы выбираем f(const A&), а не f(const T&) для вызова f(a).

Я почему-то всегда думал о шаблонной перегрузке как о "наименее специфичной". Это как если бы все наследовалось от класса Object.

class A {};
class B : public A {};

void f(const A& a);

void f(const B& a);

void f(const Object& object);


J>ЗЫ В следующий раз пиши осмысленные сабжи, плиз.


Извиняюсь, тупанул. Жаль, нельзя отредактировать.
Re[3]: перегрузка и шаблон
От: wvoquine  
Дата: 13.09.13 04:55
Оценка:
Здравствуйте, Eternity, Вы писали:

E>С другой стороны, оно несколько алогичное. Ведь шаблонная перегрузка обеспечивает точное попадание и для f(a) тоже. Если руководствоваться точным попаданием, тогда нужно как-то обосновать, почему мы выбираем f(const A&), а не f(const T&) для вызова f(a).



На эту тему есть есть примеры поприкольнее:

#include <iostream>


struct C
{
    C()
    {
        std::cout << "default C()" << std::endl;
    }
    template<typename T> C(const T& t)
    {
        std::cout << "C(T)" << std::endl;
    }
};

class D {};

int main()
{
    C c1;
    C c2(c1);
    D d;
    C c3(d);

    std::cout << "end of test" << std::endl;
}
To be is to be the value of a variable
Re[3]: перегрузка и шаблон
От: rg45 СССР  
Дата: 13.09.13 06:12
Оценка:
Здравствуйте, Eternity, Вы писали:

E>С другой стороны, оно несколько алогичное. Ведь шаблонная перегрузка обеспечивает точное попадание и для f(a) тоже. Если руководствоваться точным попаданием, тогда нужно как-то обосновать, почему мы выбираем f(const A&), а не f(const T&) для вызова f(a).


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

E>Я почему-то всегда думал о шаблонной перегрузке как о "наименее специфичной". Это как если бы все наследовалось от класса Object.


Если шаблонная перегрузка дает лучшее совпадение типов параметров, то рассуждения о специфичности неуместны.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[3]: перегрузка и шаблон
От: Vain Россия google.ru
Дата: 13.09.13 06:15
Оценка:
Здравствуйте, Eternity, Вы писали:

J>>Наоборот, шаблон способен предоставить полное попадание void f(const B& t), в то время как f(A) потребует преобразования аргумента B->A и поэтому проиграет при разрешении перегрузки.

E>Странно, что мне это не приходило в голову. Видимо, таким объяснением и руководствовались разработчики языка.
E>С другой стороны, оно несколько алогичное. Ведь шаблонная перегрузка обеспечивает точное попадание и для f(a) тоже.
При равных условиях т.е. без промежуточных преобразований, нешаблонная функция имеет приоритет над шаблонной, т.к. она как-бы более специализирована под параметр. Запомнить просто, тут всё зависит от количества промежуточных преобразований, компилятор выбирает ту функцию, где нужно меньше пребразований делать, чтобы её вызвать.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[4]: перегрузка и шаблон
От: jazzer Россия Skype: enerjazzer
Дата: 13.09.13 08:51
Оценка:
Здравствуйте, wvoquine, Вы писали:

W>Здравствуйте, Eternity, Вы писали:


E>>С другой стороны, оно несколько алогичное. Ведь шаблонная перегрузка обеспечивает точное попадание и для f(a) тоже. Если руководствоваться точным попаданием, тогда нужно как-то обосновать, почему мы выбираем f(const A&), а не f(const T&) для вызова f(a).


W>На эту тему есть есть примеры поприкольнее:

+1.
Причем иногда их можно использовать во благо: http://rsdn.ru/forum/cpp.applied/5255987.1
Автор: jazzer
Дата: 09.08.13
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Как вы думаете, является ли данное поведение перегрузки дебильным?
От: Кодт Россия  
Дата: 13.09.13 09:16
Оценка: 33 (5)
Здравствуйте, Eternity, Вы писали:

Неправильно вопрос ставишь. Надо не WTF писать, а сказать следующее:
"Пишу семейство функций, охватывающее одну или несколько иерархий классов. Хочу, чтобы это семейство работало по таким же законам, как и функции-члены — т.е. с наследованием, плюс мне нужна функция-ловушка для всего остального".

Сделать это можно несколькими способами.
1) Объединить иерарархии классов: унаследовать все базы от одной супер-базы-заглушки, а дальше просто, f(Object&)
2) Написать в функции-ловушке великое отрицание:
void f(ARoot const&);
void f(ADerived const&);
void f(AnotherRoot const&);
void f(int); // целые числа тоже, в некотором роде, можно трактовать как иерархию...

template<class T>
disable_if<
   is_base_and_derived<ARoot, T>::value
|| is_base_and_derived<AnotherRoot, T>::value
|| is_integral<T>
, void>::type
f(T const&) {.....}

3) Сделать диспетчеризацию — http://ideone.com/B1muVj
#include <iostream>
using namespace std;

#define WHOAMI() (cout << __PRETTY_FUNCTION__ << endl)

// первая иерархия
struct ARoot {};
struct ADerived1 : ARoot {};
struct ADerived2 : ARoot {};

// вторая иерархия
struct AnotherRoot {};
struct AnotherDerived : AnotherRoot {};

// провокационный вопрос
struct Ambiguous : ARoot, AnotherRoot {};

// третья, внезапная, иерархия
struct Offside {};

// основные функции
void g(ARoot const&) { WHOAMI(); }
void g(ADerived1 const&) { WHOAMI(); }
void g(AnotherRoot const&) { WHOAMI(); }
void g(int) { WHOAMI(); }

// функция-ловушка
template<class T> void g_default(T const& x) { WHOAMI(); }

// дискриминатор (SFINAE) по признаку: есть ли однозначная реализация g(x)
// плюс диспетчер
template<class T> auto h(T const& x, int) -> decltype(g(x)) { g(x); }
template<class T> auto h(T const& x, ...) -> void           { g_default(x); }

// диспетчер верхнего уровня
template<class T> void f(T const& x) { h(x, 1); }


int main() {
    // your code goes here
    f(ARoot());
    f(ADerived1());
    f(ADerived2());
    f(AnotherRoot());
    f(AnotherDerived());
    f(Ambiguous());
    f('x');
    return 0;
}

Можно было сделать изящнее — написать отдельно предикат-дискриминатор, и отдельно на enable_if/disable_if диспетчер, — и это работало бы даже на С++98. Но мне сейчас немножко лень.
Перекуём баги на фичи!
Re[2]: Как вы думаете, является ли данное поведение перегрузки дебильным?
От: Kubyshev Andrey  
Дата: 13.09.13 14:22
Оценка:
К>Неправильно вопрос ставишь. Надо не WTF писать, а сказать следующее:
К>"Пишу семейство функций, охватывающее одну или несколько иерархий классов. Хочу, чтобы это семейство работало по таким же законам, как и функции-члены — т.е. с наследованием, плюс мне нужна функция-ловушка для всего остального".


Кодт.. скажи чесно, сколько у вас на работе человек которые так вот пишут ?
Re[3]: Как вы думаете, является ли данное поведение перегрузки дебильным?
От: jazzer Россия Skype: enerjazzer
Дата: 14.09.13 00:24
Оценка:
Здравствуйте, Kubyshev Andrey, Вы писали:

KA>Кодт.. скажи чесно, сколько у вас на работе человек которые так вот пишут ?


Ну вот у нас все так пишут Других не берем
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[4]: Как вы думаете, является ли данное поведение перегрузки дебильным?
От: Kubyshev Andrey  
Дата: 14.09.13 06:38
Оценка:
KA>>Кодт.. скажи чесно, сколько у вас на работе человек которые так вот пишут ?
J>Ну вот у нас все так пишут Других не берем



Крутяки, че скажешь ..
Re[2]: Как вы думаете, является ли данное поведение перегрузки дебильным?
От: Eternity Россия  
Дата: 14.09.13 08:15
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Неправильно вопрос ставишь. Надо не WTF писать, а сказать следующее:

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

Вообще-то нет, я спросил именно о дизайне языка, так как собираюсь ради самообразования разработать очередной диалект C++. То, что решить можно, понятно. На практике можно просто перегрузки делать через enable_if. Меня интересует вопрос идеологии.
Re[4]: перегрузка и шаблон
От: Eternity Россия  
Дата: 14.09.13 08:46
Оценка:
Здравствуйте, rg45, Вы писали:

R>Для A (в отличие от B) обе перегрузки обеспечивают точное попадание. Только шаблонная перегрузка способна принять множество разных типов, а нешаблонная только один — тот, для которого она написана. Так которая из них будет более специфичной по-твоему?


Ну вот видишь, мы тут возвращаемся к понятию специфичности. Если говорить о специфичности как о количестве принимаемых типов, то перегрузка f(A) уж точно способна принять меньше типов, чем f(T).

Принцип работы C++ в этом месте я прекрасно понимаю: перегрузка без преобразования предпочитается перегрузке с преобразованием. Идея понятна, но, по-моему, это просто не совсем корректный подход для решения задачи о выборе перегрузок. Это классическое решение от реализации, а не от использования, как программист делает юзабилити программы таким, как ему проще и понятнее реализовать, а не так, как пользователю понятнее и удобнее.

Но тут спорный момент еще и в том, правильно ли считать приведение к базовому классу преобразованием. Потому что идеологически тип-наследник не нуждается в преобразовании. Класс B это тоже класс A. Стол это мебель, стол не нужно преобразовывать в мебель. Класс B это не класс, который преобразуется в класс A, класс B это уже и есть A, несмотря на то, что технически, "под капотом", преобразование необходимо.

E>>Я почему-то всегда думал о шаблонной перегрузке как о "наименее специфичной". Это как если бы все наследовалось от класса Object.


R>Если шаблонная перегрузка дает лучшее совпадение типов параметров, то рассуждения о специфичности неуместны.

.
Как я сказал выше, принцип работы C++ в этом месте мне понятен. Но с точки зрения использования языка и здравого смысла эти рассуждения о совпадении выглядят не очень связно. Функция f(T) принимает любой тип, она вообще никак не указывает что это за тип. Так почему ты говоришь о "лучшем совпадении"? Совпадении с чем? Функция f(A) принимает базовый тип для B, но ты говоришь, что оно "хуже совпадает", чем функция f(T), которая принимает какой-то тип. По-моему, f(A) лучше совпадает с B.
Re[4]: перегрузка и шаблон
От: Eternity Россия  
Дата: 14.09.13 08:48
Оценка:
Здравствуйте, Vain, Вы писали:

V>При равных условиях т.е. без промежуточных преобразований, нешаблонная функция имеет приоритет над шаблонной, т.к. она как-бы более специализирована под параметр. Запомнить просто, тут всё зависит от количества промежуточных преобразований, компилятор выбирает ту функцию, где нужно меньше пребразований делать, чтобы её вызвать.


Ответил здесь http://rsdn.ru/forum/cpp/5295465.1
Автор: Eternity
Дата: 14.09.13
Re[5]: перегрузка и шаблон
От: wvoquine  
Дата: 14.09.13 09:09
Оценка:
Здравствуйте, Eternity, Вы писали:

E>Но тут спорный момент еще и в том, правильно ли считать приведение к базовому классу преобразованием. Потому что идеологически тип-наследник не нуждается в преобразовании. Класс B это тоже класс A. Стол это мебель, стол не нужно преобразовывать в мебель. Класс B это не класс, который преобразуется в класс A, класс B это уже и есть A, несмотря на то, что технически, "под капотом", преобразование необходимо.


По-моему, по этой логике следующий код должен ломаться на компиляции:

class A {};
class B : public A {};

void f(const A& a) {}
void f(const B& a) {}


int main() 
{
    
    B b;
    f(b);
    
    return 0;
}
To be is to be the value of a variable
Re[6]: перегрузка и шаблон
От: Eternity Россия  
Дата: 14.09.13 09:31
Оценка:
Здравствуйте, wvoquine, Вы писали:

E>>Но тут спорный момент еще и в том, правильно ли считать приведение к базовому классу преобразованием. Потому что идеологически тип-наследник не нуждается в преобразовании. Класс B это тоже класс A. Стол это мебель, стол не нужно преобразовывать в мебель. Класс B это не класс, который преобразуется в класс A, класс B это уже и есть A, несмотря на то, что технически, "под капотом", преобразование необходимо.


W>По-моему, по этой логике следующий код должен ломаться на компиляции:

  Скрытый текст
W>
W>class A {};
W>class B : public A {};

W>void f(const A& a) {}
W>void f(const B& a) {}


W>int main() 
W>{
    
W>    B b;
W>    f(b);
    
W>    return 0;
W>}
W>

Здрасьте, Новый год. "По этой логике" этот код должен ломаться не более, чем вот этот код:

#include <iostream>

using namespace std;

class A {};

class B : public A {};

void f(const A& a) {
    cout << "f(A);" << endl;
}

template <class T>
void f(const T& t) {
    cout << "f(T);" << endl;
}

int main() {
    A a;
    f(a);
    return 0;
}


(Так как здесь типы тоже "точно совпадают".)
Re[5]: Как вы думаете, является ли данное поведение перегрузки дебильным?
От: Eternity Россия  
Дата: 14.09.13 09:37
Оценка:
Здравствуйте, Kubyshev Andrey, Вы писали:

KA>Крутяки, че скажешь ..


Много че можно сказать...
Re[5]: перегрузка и шаблон
От: wvoquine  
Дата: 14.09.13 09:42
Оценка:
Здравствуйте, Eternity, Вы писали:


E>.

E>Как я сказал выше, принцип работы C++ в этом месте мне понятен. Но с точки зрения использования языка и здравого смысла эти рассуждения о совпадении выглядят не очень связно. Функция f(T) принимает любой тип, она вообще никак не указывает что это за тип. Так почему ты говоришь о "лучшем совпадении"? Совпадении с чем? Функция f(A) принимает базовый тип для B, но ты говоришь, что оно "хуже совпадает", чем функция f(T), которая принимает какой-то тип. По-моему, f(A) лучше совпадает с B.


В том то и дело, что она не принимает какой-то тип — какого-то типа не существует. Шаблон функции — это, говоря языком формальной логики, схема функций, и аргументы у него схематичны. То есть нет этого какого-то аргумента, а есть схемы, в которые мы можем подставить какие-то аргументы, и схема инстанцируется в нечто конкретное, с конкретным типом аргумента. То есть для каждого подставленного типа аргумента генерируется отдельная функция, и это не просто деталь реализации, это и есть схема по своему смыслу.

Потому и получается точное совпадение — с инстанцированной по типу схемой.
To be is to be the value of a variable
Re[7]: перегрузка и шаблон
От: wvoquine  
Дата: 14.09.13 09:49
Оценка:
Здравствуйте, Eternity, Вы писали:

E>Здравствуйте, wvoquine, Вы писали:


E>Здрасьте, Новый год. "По этой логике" этот код должен ломаться не более, чем вот этот код:


E>
E>#include <iostream>

E>using namespace std;

E>class A {};

E>class B : public A {};

E>void f(const A& a) {
E>    cout << "f(A);" << endl;
E>}

E>template <class T>
E>void f(const T& t) {
E>    cout << "f(T);" << endl;
E>}

E>int main() {
E>    A a;
E>    f(a);
E>    return 0;
E>}


Как я и писал, не нужно путать шаблонные аргументы с конкретными базовыми типами.

E>(Так как здесь типы тоже "точно совпадают".)


Вот тут как раз точно совпадают в отличие от того случая (где требовалось преобразование). И выходом из этого было при точном совпадении (а шаблон только такое и дает) сделать нешаблонные функции более приоритетными.
To be is to be the value of a variable
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.