palm_mute ([info]palm_mute) wrote,
@ 2008-03-05 12:40:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:c++

Загадка
Пришлось по долгу службы вспоминать язык программирования Ц++. До чего же быстро забываются все эти overload resolution'ы, template argument deduction'ы, one definition rule'ы, 2-phase name lookup'ы и пр. без активного использования.

Рассмотрим код единицы трансляции ниже:

#include <iostream>

template <class T>
void f()
{
    std::cerr << "yo whazzup" << std::endl;
}

void g()
{
    f<int>();
}

Рассмотрели? Кто может, не подглядывая в "C++ Templates: The Complete Guide" или в священную корову Стандарт, вообразить ситуацию, в которой замена void на int в объявлении шаблона функции f влияет на вывод функции g?
Дополнение: в коде данной единицы трансляции ничего менять нельзя (кроме типа возвращаемого значения f, естественно).

з.ы. совсем не исключаю, что это все знают, а я один такой деревянный; но язык, в котором такое возможно, - это кошмар, по-моему.


Update.
Внимание, правильный ответ:

Я старался формулировать осторожно, потому надеюсь, что ни для кого не будет сюрпризом, что в загадке приведен не весь код - а именно, опущен код еще одной единицы трансляции (слово "ситуация" подразумевало такую возможность). Предположим, что код из загадки находится в файле b.cpp, а код ниже - в файле a.cpp

#include <iostream>

template <class T>
void f()
{
    std::cerr << "surprise!" << std::endl;
}

template void f<int>();

void g();

int main (int argc, char* argv[])
{    
    g();
    return 0;
}


Программа, скомпилированная командой g++ a.cpp b.cpp (g++ (GCC) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)) печатает "yo whazzup" или "surprise!" в зависимости от типа возвращаемого значения шаблона функции f, определенного в b.cpp.
Я понимаю это так: в разделе 12.2.1 книги "C++ Templates: The Complete Guide" (думаю, можно найти объяснение, основываясь прямо на тексте стандарта, но мне лень) перечисляются случаи, при которых в C++ могут сосуществовать в одной программе одноименные функции. Ключевая фраза:
"Two functions can coexist in a program if they have distinct signatures. We define the signature of a function as the following information: ... Its return type, if the function is generated from a function template". Таким образом, определение шаблонов template <class T> void f() в обеих единицах трансляции нарушает one definition rule, что приводит к Undefined Behaviour. g++ имел право в этом случае залить соседей или вызвать ОМОН, но он просто вызвал ту функцию, которая ему больше понравилась.
Пусть знающие товарищи меня поправят.


(Post a new comment)

не вкуриваю
[info]nealar
2008-03-05 11:27 am UTC (link)
Каким образом меняет?

(Reply to this)(Thread)

Re: не вкуриваю
[info]palm_mute
2008-03-05 11:36 am UTC (link)
функция g при вызове может напечатать "yo whazzup". А может и не напечатать.

(Reply to this)(Parent)

Re: не вкуриваю
[info]palm_mute
2008-03-05 01:34 pm UTC (link)
см. update

(Reply to this)(Parent)


[info]dtim
2008-03-05 11:33 am UTC (link)
Я, видать, тоже деревянный. Что за ситуация?

(Reply to this)(Thread)


[info]palm_mute
2008-03-05 11:35 am UTC (link)
я немного повременю с правильным ответом, вдруг кто-то угадает

(Reply to this)(Parent)


[info]palm_mute
2008-03-05 01:34 pm UTC (link)
см. update

(Reply to this)(Parent)


[info]vidmich
2008-03-05 12:17 pm UTC (link)
VS2K8 выводит нормально при void и int. Сомневаюсь что есть "ситуация" где это изменится...

(Reply to this)(Thread)


[info]palm_mute
2008-03-05 01:35 pm UTC (link)
см. update

(Reply to this)(Parent)


[info]deni_ok
2008-03-05 12:25 pm UTC (link)
(5 минут раздумий)

Ну уж нет. Размышления в эту сторону на C++ - это для монстров. Почему уже готовый код работает непонятным образом - это нормальный C++-challenge, на это мы способные :)

Колись давай ;-)

(Reply to this)(Thread)


[info]palm_mute
2008-03-05 01:35 pm UTC (link)
см. update

(Reply to this)(Parent)(Thread)


[info]deni_ok
2008-03-05 01:52 pm UTC (link)
VC2005 аналогично.

Сейчас тебе скажут, что все случаи UB мастер C++ должен, эта, за версту чуять ;-) И что только лохи неотесанные ODR нарушают...

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-05 01:57 pm UTC (link)
Этточно, как говорил товарищ Сухов.

Воинственные товарищи уже появились (непонятно, как они набрели на мой несчастный журнальчик).

(Reply to this)(Parent)(Thread)


[info]deni_ok
2008-03-05 02:20 pm UTC (link)
Классный этюдик, но его никуда не запостить на широкую публику - фанаты будут напирать на спасительный UB. Формально-то задача не решена. Я где-то по другому поводу пытался выяснить, а нельзя ли сузить область применения этой мантры, типа компилер/линкер могли бы и отлавливать во многих случаях. Остался непонят :)

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-05 04:24 pm UTC (link)
Я тоже не понимаю, почему линкер не может сравнивать хотя бы каие-то хеши, вычисляемые по телу функций, при конфликте имен. Молча выбрасывать - очень странное решение. You don't pay for what you don't use, блин.

(Reply to this)(Parent)

Афтар убей сибя ап стену
[info]bahcha
2008-03-05 12:57 pm UTC (link)
Помоему у тебя проблемы не только с с++ но и с русским тоже. Это тебе не С# осваивать - тут думать надо

(Reply to this)(Thread)

кто здесь?
[info]palm_mute
2008-03-05 01:00 pm UTC (link)

по существу вопроса есть что сказать?

з.ы. после следующего "афтара" ты будешь в бане :)

(Reply to this)(Parent)(Thread)

Re: кто здесь?
[info]bahcha
2008-03-05 01:03 pm UTC (link)
Приведи код, который у тебя не работает - или работает не так как ты ожидаешь. Потому как из твоего описания это не понятно

(Reply to this)(Parent)(Thread)

Re: кто здесь?
[info]volodymir_k
2008-03-05 01:09 pm UTC (link)
#include <iostream>

template <class T>
int f()
{
    std::cerr << "yo whazzup" << std::endl;
}

void g()
{
    f<int>();
}

(Reply to this)(Parent)(Thread)

Re: кто здесь?
[info]vidmich
2008-03-05 01:16 pm UTC (link)
Этот код даже не скомпилится. так как f должна возвращать щначение какое-нить...

(Reply to this)(Parent)

Re: кто здесь?
[info]bahcha
2008-03-05 01:16 pm UTC (link)
Ну этот код вообще не будет компилироваться. Хочеться все таки услышать автора. Можно переформулировать вопрос без вычурных фраз, вроде "вы можете себе представить ситуацию ...." и тп. В простых однозначных предложениях. И привести авторский код...

(Reply to this)(Parent)(Thread)

Re: кто здесь?
[info]palm_mute
2008-03-05 01:34 pm UTC (link)
1) gcc, например, при отсутствии return код компилирует (но выдает warning), хотя это к делу не относится.
2) см. update

(Reply to this)(Parent)(Thread)

(Deleted post)

[info]palm_mute
2008-03-05 01:51 pm UTC (link)
что такое "выбор возвращаемого значения" и что вы хотите этим сказать?

(Reply to this)(Parent)(Thread)


[info]bahcha
2008-03-05 01:54 pm UTC (link)
f<int>() никакого отношения к выбору возвращаемого значения не имеет

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-05 01:58 pm UTC (link)
вопрос остался в силе - я не понял, что вы хотите этим сказать, разверните, пожалуйста.

(Reply to this)(Parent)(Thread)


[info]bahcha
2008-03-05 02:19 pm UTC (link)
Если ты пытаешься вызвать
template <class A> int f();
вместо
template <class A> void f();
то
f<int>();
этого не делает, т.к. explicit template argument не имеет никакого отношения к возвращаемому типу в данном случае. Так что все что ты постарался процетировать из стандарта - это пьеса из другой оперы.

А в твоем случае можно было использовать частичную специализацию и все.

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-05 02:28 pm UTC (link)
я все это понимаю. речь идет именно о "пьесе из другой оперы", как ты выразился. эффект проявляется не только при замене void на int, но и при замене на любой другой тип; я выбрал int, чтобы запутать.

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

(Reply to this)(Parent)(Thread)


[info]bahcha
2008-03-05 02:37 pm UTC (link)
Люди с наличием мозгов, создают в каждом модуле свой namespace, чтобы таких проблем не возникало. А любители - всегда полагаются на default behavior. А за нарушением ODR должен следить компилятор, так что это проблема gcc, а не С++.

И по прежнему не понятно, причем тут тип возвращаемого значения в твоем посте.

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-05 02:45 pm UTC (link)
> И по прежнему не понятно, причем тут тип возвращаемого значения в твоем посте.

Что ж тут непонятного, я же цитату с объяснением привел. Типом возвращаемого значения определяется, нарушаем мы ODR или нет.

Всего хорошего.

(Reply to this)(Parent)(Thread)


[info]bahcha
2008-03-05 02:55 pm UTC (link)
Привожу совершенно легальный код, который ничего не нарушает и подпадает под оригинальный вопрос. И нефиг городить про нарушение ODR...

#include <iostream>

template <class A> void f();

template <>
void f<int>()
{
std::cout << "Two" << std::endl;
}

//--------------------------------------
template <class A>
void f()
{
std::cout << "One" << std::endl;
}

void g()
{
f<int>();
}

int main(int argc, сhar* argv[])
{
g();
return 0;
}

(Reply to this)(Parent)


[info]kodt_rsdn
2008-03-05 02:09 pm UTC (link)
Тю... Я-то думал, ты спрашиваешь про легальную ситуацию...
Конечно, на нарушении ODR можно и не такого наворотить.

Но хочу заметить, что нарушение ODR - это не есть свойство только языка С++.
Другой известный пример этого же антипаттерна - DLL hell.
Или, абсолютно на любом языке, поддерживающем COM или CORBA, можно реализовать нарушение ODR: объявить одноимённые интерфейсы в двух местах по-разному, и крах вам обеспечен.

А природа явления - слабая идентификация интерфейсов (только по имени).
Вот если бы идентифицировать интерфейс (абстрактный класс в COM/CORBA и шаблон класса/функции в С++) не только сигнатурой, но и цифровой подписью содержимого...

... то на уровне DLL это делается манифестами, и говорят, что уже есть проблема manifest hell.

(Reply to this)(Thread)


[info]palm_mute
2008-03-05 02:20 pm UTC (link)
> Тю... Я-то думал, ты спрашиваешь про легальную ситуацию...
> Конечно, на нарушении ODR можно и не такого наворотить.

Ну, извини :). Ты, видимо, C++ забыть не успел.
Меня удивила легкость, с которой можно нарушить ODR. Когда в каком-нибудь модуле foo.cpp на скорую руку пишешь вспомогательную функцию, последнее, о чем задумываешься - это о возможности нарушить ODR. Тем более, когда пишешь шаблон, который обычно можно вызвать, только если видно его определение (забудем пока об экспорте шаблонов и explicit instantiation). Достаточно пару месяцев писать на Java/C#/OCaml/Haskell/что угодно с нормальной системой модулей, и такая возможность вылетает из головы.

Я понимаю, что достаточно все такие вспомогательные функции выносить в unnamed namespace, но осадок остался.

>А природа явления - слабая идентификация интерфейсов (только по имени).
Это для глобально видимых интерфейсов. А здесь не хватает банальной системы модулей.

(Reply to this)(Parent)(Thread)


[info]bahcha
2008-03-05 02:34 pm UTC (link)
Люди с наличием мозгов, создают в каждом модуле свой namespace, чтобы таких проблем не возникало. А любители - всегда полагаются на default behavior

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-05 02:36 pm UTC (link)
люди с наличием мозгов не грубят незнакомым людям при обсуждении технических вопросов.

(Reply to this)(Parent)(Thread)


[info]vorotylo
2008-03-05 09:51 pm UTC (link)
do not feed the troll

(Reply to this)(Parent)


[info]deni_ok
2008-03-05 02:42 pm UTC (link)
Люди с наличием мозгов выбирают язык, авторы которого позаботились о том, чтобы в каждом модуле был свой namespace :)

Ваш ход, сударь ;-)

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-05 02:44 pm UTC (link)
Его новых ходов здесь не предвидится (под старым ником, по крайней мере).

(Reply to this)(Parent)(Thread)


[info]deni_ok
2008-03-05 02:50 pm UTC (link)
Чёрт, а я только расчехлил свой Big Flame Gun!

(Reply to this)(Parent)(Thread)

:)
[info]palm_mute
2008-03-05 02:53 pm UTC (link)
Что за удовольствие флеймить, если твои ответы не читают и не воспринимают.

(Reply to this)(Parent)


[info]kodt_rsdn
2008-03-06 11:35 am UTC (link)
В большинстве языков один модуль - один файл. А в C и С++ нет ни модулей, ни файлов - только единицы компиляции :)

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-06 03:15 pm UTC (link)
>В большинстве языков один модуль - один файл
Ну да. Простая и вполне рабочая идея, надо сказать. Есть еще дотнет с его сборками, пространствами имен и частичными классами.

>А в C и С++ нет ни модулей, ни файлов - только единицы компиляции :)
Оттого и проблемы на ровном месте.

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-06 03:25 pm UTC (link)
>Есть еще дотнет с его сборками, пространствами имен и частичными классами.
И, конечно же, ява с пакетами

(Reply to this)(Parent)


[info]migmit
2008-03-05 08:52 pm UTC (link)
Что такое ODR?

(Reply to this)(Thread)


[info]palm_mute
2008-03-05 09:10 pm UTC (link)
One Definition Rule.
Этому правилу посвящен раздел 3.2 стандарта ISO/IEC 14882:2003(E), примерно 2 страницы. Вот в двух словах основная идея:
"No translation unit shall contain more than one definition of any variable, function, class type, enumeration type or template...
Every program shall contain exactly one definition of every non-inline function or object that is used in that program; no diagnostic required..."

(Reply to this)(Parent)


[info]some41
2008-03-05 10:30 pm UTC (link)
ну что-то ты правда сдеревянничал. одно имя в разных файлах можно было и проще получить, без шаблонов и явного указания параметров.

это еще фигня, мне тоже приходятся сейчас на С++ писать, там гораздо больше радости встречается. особенно весело когда код должен работать на 4 компиляторах, включая vc6, который шаблоны поддерживает через раз.

(Reply to this)(Thread)


[info]palm_mute
2008-03-06 09:09 am UTC (link)
> одно имя в разных файлах можно было и проще получить, без шаблонов и явного указания параметров.
с шаблонами интереснее - менее очевидно, что имена совпали.

> ну что-то ты правда сдеревянничал
не спорю. я же предупреждал - загадка для почти забывших C++. Ты, если не ошибаюсь, компилятор C писал?

> включая vc6, который шаблоны поддерживает через раз
помню-помню. ICE на каждом шагу.

(Reply to this)(Parent)(Thread)


[info]kodt_rsdn
2008-03-06 11:59 am UTC (link)
Там ещё и компилятор умел нарушать ODR по собственной инициативе - как раз на воплощении шаблона функции, сигнатура которого не зависит от параметров шаблона
template<class T> void foo() { cout << typeid(T).name() << endl; }

int main()
{
  foo<int>();  // int
  foo<char>(); // int
}

(чорт, совсем забыл, что LJ жёстко обращается с невалидным html).

(Reply to this)(Parent)(Thread)


[info]palm_mute
2008-03-06 03:19 pm UTC (link)
Ой-ё. Да, давно я не брал в руки шашку (то-бишь MSVC 6). Теперь вспоминаю, что у нас многие шаблонные функции именно по этой причине выглядели так:
template <class T> void foo(T* dummy=0) { //... }

(Reply to this)(Parent)


[info]some41
2008-03-06 11:30 pm UTC (link)
это недостаток в схеме манглирования. вообще в реализациях С++ все просто - что манглируется в одну строку, то и одинаковое. убицца тапком if you think about it. зато можно подобные теме штуки (тип возвращаемого значения влияет на ODR) легко определять при помощи nm.

(Reply to this)(Parent)


[info]some41
2008-03-06 11:26 pm UTC (link)
можно случайно классы одинаково назвать. у них склеятся конструкторы и будет полный конец обеда.
причем еще, как и в твоем случае, на -O2 все будет работать.

а С и С++ все-таки совсем разные языки. мне больше всего С99 нравится.

а vc6, например, требует, чтобы explicit instantiation были _до_ specializations, то есть строго против стандарта.

(Reply to this)(Parent)


Create an Account
Forgot your login?
Login w/ OpenID
English • Español • Deutsch • Русский…