Форум программистов, компьютерный форум, киберфорум
Наши страницы
С++ для начинающих
Войти
Регистрация
Восстановить пароль
 
студ
0 / 0 / 1
Регистрация: 05.09.2014
Сообщений: 65
#1

Особенности перегрузки оператора "запятая" - C++

27.03.2015, 02:57. Просмотров 810. Ответов 10
Метки нет (Все метки)

Препод на защите прошлой лабы задал вопрос - чем отличается перегрузка оператора запятая от других... Перерыл кучу статей, пишут только, что сам оператор специфичный и что перегружать его можно только в классе.
Почему только в классе? И есть ли у него еще какие-нибудь особенности при перегрузке? А то завтра на лабу, надо отвечать...
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
27.03.2015, 02:57
Я подобрал для вас темы с готовыми решениями и ответами на вопрос Особенности перегрузки оператора "запятая" (C++):

Реализация собственного оператора "запятая"
Всем привет. Подскажите как самому реализовать оператор запятая. Я хочу...

Необходимость перегрузки оператора присваивания "="
Собственно название темы и есть вопрос)) Скажите пожалуйста в чем же...

Утечка при перегрузки оператора "+"
Делаю велосипед под названием andString (string) andString.h class...

Выяснить, имеется ли пара соседствующих символов ",-" "-," (запятая, тире) в тексте
#include <iostream.h> //Уважаемые программисты помогите переделать эту //Вот...

Перегрузка оператора "++" и "--" в чем может быть причина нарушение прав доступа?
Вот такой вот код #include <iostream> class d { private: int size ;...

10
hoggy
Заблокирован
27.03.2015, 03:38 #2
Цитата Сообщение от студ Посмотреть сообщение
Перерыл кучу статей, пишут только, что сам оператор специфичный и что перегружать его можно только в классе.
выбросите эти статьи на помойку.
(он прекрасно перегружается глобально)

оператор запятая действительно обладает уникальным свойством:
это - единственный способ в языке,
с помощью которого можно разрулить ситуацию с типами,
для которых невозможно создать объект.

но это тема явно не для новичка.
поэтому слушайте внимательно:

область применения: метапрограммирование.
изготовление механизмов класса "динамический делегат".

что бы понять ниже представленный материал вам нужно понять, что такое "функтор".

функторы - это классы, объекты которых внешне умеют вести себя подобно функциям:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
 
struct Foo
{
    void operator()()const { std::cout<<"Foo();\n"; }
};
    
struct Bar
{
    int operator()()const { return std::cout<<"Bar();\n", 1; }
};
    
 
 
int main()
{
    std::cout << "Hello, world!\n";
    
    Bar b;
    Foo f;
    
    b();  //<--- обратите внимание
    f();  //внешне похоже на вызов функции
    
    //достигается за счет оператор()
}
теперь, предположим, нужно создать шаблоно-функцию,
которая в качестве параметров принимает тип функтора,
дергает его, и выводит результат в консоль:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
 
struct Foo
{
    void operator()()const { std::cout<<"Foo();\n"; }
};
    
struct Bar
{
    int operator()()const { return std::cout<<"Bar();\n", 1; }
};
    
 
template<class T> void Test()
{
    T functor;
      
    std::cout<< functor() << std::endl;
    
}
 
 
int main()
{
    std::cout << "Hello, world!\n";
    
    Test<Bar>();
}
вывод:
Hello, world!
Bar();
1
Пока все нормально.

Но если мы попытаемся всунуть в шаблон Foo, программа сломается:

C++
1
Test<Foo>();
мы получим портянку ошибок компиляции.

это связанно с тем, что функтор Foo возвращает void:

C++
1
2
3
4
struct Foo
{
    void operator()()const { std::cout<<"Foo();\n"; }
};
но у типа void вообще не может быть объектов, и поэтому результат работы нельзя отправить в std::cout

вообще на языке с++ существует ровно два места,
где можно использовать void в качестве возвращаемого значения:

1. можно вернуть его из функции, которая сама возвращает void:

C++
1
2
3
4
5
void Example()
{
    Foo f;
    return f();
}
либо, можно использовать его в оператор запятая.

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

а именно:
если в шаблоне функтор возвращает void,
то с помощью оператор запятая происходит подмена этого типа на некоторый другой,
который символизирует "пустоту", "отсутствие объекта".

возвращаясь к нашему сломанную примеру выше,
при помощи оператор запятая, проблему можно было бы решить так:

http://rextester.com/DKJM99180
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
 
 
struct SEmpty
{
    template<class T>friend ::std::basic_ostream<T>& 
        operator<<(::std::basic_ostream<T>& os, const SEmpty&)
            { return os<< "empty"; }
};
 
//выделено в отдельный спейс, 
// с целью локализировать эффект переопределения оператора запятая
namespace private_operator_comma
{
    //смысл переопределения оператора запятая заключается в следующем:
    //если T окажется void, то вернется SEmpty, иначе вернется T
    template<class T>T& operator, (T& val, const SEmpty empty) 
       { (void)empty; return val; }
    template<class T>const T& operator, (const T& val, const SEmpty empty) 
       { (void)empty; return val; }
}
 
 
struct Foo
{
    void operator()()const { std::cout<<"Foo(): "; }
};
    
struct Bar
{
    int operator()()const { return std::cout<<"Bar(): ", 1; }
};
    
 
// T - функтор
// если функтор возвращает void, то вернется SEmpty
// иначе - вернется тип, который возвращает оператор() функтора
template<class T>
void Test()
{
    using namespace private_operator_comma;
    
    T functor;
    
    //переопределенный оператор запятая корректно разруливает случай, когда R = void
    std::cout<< ( functor() , SEmpty()) << std::endl;
    
}
 
 
int main()
{
    std::cout << "Hello, world!\n";
    
    Test<Bar>();
    
    Test<Foo>();
}
9
MrGluck
Модератор
Эксперт CЭксперт С++
7980 / 4861 / 1422
Регистрация: 29.11.2010
Сообщений: 13,234
27.03.2015, 10:59 #3
Comma operator например используют в Boost.Spirit для инициализации набора последовательности. То есть он выступает в качестве разделителя. Однако в "классическом С++" он применяется например в циклах, когда задаётся предусловие или постусловие, совершенно в другом смысле.
C++
1
for (size_t i=0, len = str.length(); i < len; i++)
Предполагаю, что фишка в перегрузке этого оператора в том, что его смысл может трактоваться по разному, в отличии от других операторов, где их предназначение предопределено.

Тут всё хорошо разжёвано (касательно применения):
http://stackoverflow.com/questions/5...-operator-work
http://www.wikiwand.com/en/Comma_operator
1
Black Fregat
2396 / 1211 / 327
Регистрация: 31.05.2009
Сообщений: 4,805
08.11.2015, 12:13 #4
hoggy, правильно ли я понял, что в Вашем коде шаблоны в строках 17-20 не применяются к типу void из-за указания <class T>?

А какое назначение у конструкции (void)empty;? Без неё, вроде, тоже всё работает..
0
Croessmah
++Ͻ
14146 / 8071 / 1512
Регистрация: 27.09.2012
Сообщений: 19,905
Записей в блоге: 3
Завершенные тесты: 1
08.11.2015, 12:22 #5
Цитата Сообщение от Black Fregat Посмотреть сообщение
Без неё, вроде, тоже всё работает..
но с предупреждениями
0
Black Fregat
2396 / 1211 / 327
Регистрация: 31.05.2009
Сообщений: 4,805
08.11.2015, 12:30 #6
Да вроде без предупреждений..
Код
G:\Loc\C>g++ -std=c++0x -Wall comma.cpp -o comma


G:\Loc\C>comma
Hello, world!
Bar(): 1
Foo(): empty

G:\Loc\C>
0
hoggy
Заблокирован
08.11.2015, 12:30 #7
Цитата Сообщение от Black Fregat Посмотреть сообщение
не применяются к типу void из-за указания <class T>?
не применяются к типу void потому что это против правил языка с++.

изначально существует глобальный
(дефолтно-существующий, и никем не переопределенный)
оператор запятая.

суть его простая:

C++
1
2
3
 (foo(),bar());  //<--- сначала выполняется foo(), затем bar(), 
//и в качестве результата всего выражение то, что справа
//таким образом по дефолту результат выражения будет bar()
другой пример:

C++
1
2
void foo() { std::cout<<"void\n"; }
foo(),10);
выведет в каут "void", но результат выражения - то, что справа, тобишь 10.

итого: учитывается только результат того, что справа,
поэтому все левосторонние результаты отбрасываются, как не используемые.

именно этот фактор и позволяет оператор запятой переварить void.

теперь, касательно кода:
своими шаблоно-перегрузками,
я явным образом переопределяю логику этого поведения.

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

поэтому, если в эти шаблоны подсунуть void, они просто не скомпилируются.

но согласно SFINAE, компилятор не делает ошибки компиляции для шаблонов,
если теоретически существуют такие параметры,
при которых шаблон сможет скомпилироваться успешно.

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

ошибка компиляции будет только и только
если не подошел ни один из возможных претендентов.

и таким образом получается:
либо там не void, и тогда мой шаблон подойдет, и вернется левостороннее значение.

либо там void, тогда шаблоны провалятся, и сработает глобально-дефолтная перегрузка,
которая вернет правостороннее значение.
2
Black Fregat
2396 / 1211 / 327
Регистрация: 31.05.2009
Сообщений: 4,805
08.11.2015, 12:32 #8
То есть, это для исключения предупреждения о неиспользуемом параметре?
0
hoggy
Заблокирован
08.11.2015, 12:32 #9
Цитата Сообщение от Black Fregat Посмотреть сообщение
А какое назначение у конструкции (void)empty;? Без неё, вроде, тоже всё работает..
компиляторы от вижал студии агрятся предупреждениями на "не используемое значение".

это:
C++
1
(void)value;
указивка компилятору, что мы в курсе,
и не нужно нас доставать глупыми предупреждениями.


причем, иногда, даже если вычеркнуть имя из прототипа - все равно агряццо.
это типа глюки у компилятора на тяжелых шаблонах.
1
DrOffset
7517 / 4513 / 1097
Регистрация: 30.01.2014
Сообщений: 7,362
08.11.2015, 13:02 #10
Цитата Сообщение от Black Fregat Посмотреть сообщение
Да вроде без предупреждений..
-Wextra
1
Dreamer_0x01
258 / 85 / 30
Регистрация: 29.10.2015
Сообщений: 192
08.11.2015, 13:26 #11
hoggy, хорошо материал излагаете!
Даже имея казалось бы большой опыт программирования с большим интересом почитал.
1
08.11.2015, 13:26
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
08.11.2015, 13:26
Привет! Вот еще темы с решениями:

В зависимости от времени года "весна", "лето", "осень", "зима" определить погоду "тепло", "жарко", "холодно", "очень холодно"
В зависимости от времени года &quot;весна&quot;, &quot;лето&quot;, &quot;осень&quot;, &quot;зима&quot; определить...

Перегрузка оператора "++" и "--" , компилирует, но при запуске программы - ошибка
#include &lt;iostream&gt; #include&lt;ctime&gt; #include&lt;stdio.h&gt; #include&lt;locale.h&gt;...

Класс "Матрица". Перегрузка оператора "минус"
Помогите дописать программу или исправить ее. Нужно было написать программу,...

Не корректно считает перегрузку оператора "-" после "+"
Доброго дня. Помогите советом. Не корректно считает a - b и a * b , после...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
11
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru